LLM_EXPORT_VERSION: 1 GENERATED_AT_UTC: 2026-09-25T00:49:01.158Z DOC_COUNT: 18 === DOCUMENT START === SOURCE_PATH: docs/adapters.mdx METADATA: title: Hosted Samsar.js & deployment adapters description: Start with hosted Samsar.js, then configure supported native and provider adapters for a standalone Samsar deployment. slug: /adapters sidebar_label: Deployment adapters PRIMARY_HEADING: Hosted Samsar.js & deployment adapters CONTENT_START # Hosted Samsar.js & deployment adapters Use **Samsar.js with the hosted Samsar API** as the starting point for media, workflows, search and delivery. When you operate a standalone installation, the same workflows can use supported local provider adapters. Native inference and custom model endpoints can take precedence where their model contract requires it. ## Choose your path | Path | What you configure | Where work runs | Start here | | --- | --- | --- | --- | | Hosted Studio | Samsar account | Managed Samsar services | [Open Studio](https://app.samsar.one) | | Hosted API with Samsar.js | Samsar API key in your server environment | Managed Samsar services and their upstream providers | [SDK quick start](/#make-your-first-request) | | Standalone with Samsar.js adapter | Local Samsar installation + `SAMSAR_API_KEY` | Local orchestration; supported generation calls use hosted Samsar | [External requests](/external-requests) | | Standalone with native/provider adapters | Local installation + the supported provider credentials | Local orchestration; model calls use configured providers | [Model matrix](/models) | | Custom model endpoint | Compatible endpoint, authorization and model configuration | Your model endpoint and local Samsar orchestration | [Custom image utility](https://github.com/samsarone/samsar/blob/main/utils/vast-flux2/README.md) | Calling the hosted API and selecting the Samsar adapter inside a standalone deployment are two different uses of Samsar.js. The SDK is a client; it does not deploy a model or turn provider credentials into a running service. ## Start with hosted Samsar.js ```bash npm install samsar-js ``` ```js const samsar = new SamsarClient({ apiKey: process.env.SAMSAR_API_KEY }); const job = await samsar.createVideoFromText({ prompt: 'A calm travel film about a morning beside the sea.', image_model: 'GPTIMAGE2.5', video_model: 'RUNWAYML', duration: 30, aspect_ratio: '16:9', tts_model: 'OPENAI', backingtrack_model: 'LYRIA3', }); // Save this ID. Generation continues asynchronously. const requestId = job.data.request_id; const status = await samsar.getStatus(requestId); console.log(status.data); ``` The default base URL is `https://api.samsar.one/v1`. Keep the key on your server. Poll according to the [Video API](/video#get-status), or pass a webhook where supported. Hosted charges use Samsar credits; [pricing](/pricing) and response credit headers describe billing.
Use the same SDK against a standalone processor ```js const local = new SamsarClient({ apiKey: process.env.SAMSAR_LOCAL_API_KEY, baseUrl: 'http://localhost:3002/v1', }); const available = await local.getSupportedTextToVideoModels(); console.log(available.data); ``` Use a key accepted by that local processor. Changing `baseUrl` changes the API host; it does not configure provider credentials. Add those in the setup wizard on the standalone installation.
## Expand into supported adapters Open **Providers** in the [setup wizard](https://github.com/samsarone/samsar/blob/main/pages/setup-wizard.md), add the credentials for your intended models, and let validation produce the available model list. Start with the Samsar adapter for its supported managed routes; add native inference or other provider adapters as needed. - **Samsar.js**: Call the hosted Samsar service using Samsar credits. The samsar-js client is the common entry point. Credentials: SAMSAR_API_KEY. Docs: https://docs.samsar.one/external-requests - **OpenAI**: Native GPT inference, image and speech adapters; also required for local embedding indexes. Credentials: OPENAI_API_KEY. Docs: https://platform.openai.com/docs/overview - **Anthropic**: Native Claude inference and assistant adapter. Credentials: ANTHROPIC_API_KEY. Docs: https://platform.claude.com/docs/en/api/overview - **Google Cloud**: Gemini, Nano Banana, Veo, Google speech and Lyria. Music credentials and model access are validated separately. Credentials: Google service account / configured Google credentials. Docs: https://cloud.google.com/vertex-ai/generative-ai/docs - **Kimi**: Native Kimi text, vision and structured output; Samsar is the supported alternative. Credentials: KIMI_K3_API_KEY. Docs: https://platform.kimi.ai/docs/overview - **Alibaba Cloud**: Native Qwen inference in Docker, Qwen Image, Wan and Happy Horse. Qwen Image requires standard pay-as-you-go access. Credentials: ALIBABA_API_KEY. Docs: https://www.alibabacloud.com/help/en/model-studio/ - **GMICloud via GenBlaze**: Only exact model and operation mappings validated for your credential are enabled by the local GenBlaze gateway. Credentials: GMI_API_KEY. Docs: https://docs.gmicloud.ai/ Adapter: https://github.com/backblaze-labs/genblaze - **fal**: Supported media, speech, music, lip-sync and sound-effect adapters. Credentials: FAL_API_KEY. Docs: https://fal.ai/docs/documentation - **OpenRouter**: Supported inference and vision routes. This does not enable image, video or audio generation. Credentials: OPENROUTER_API_KEY. Docs: https://openrouter.ai/docs/quickstart - **ElevenLabs**: Direct music adapter. Speech is enabled through fal in the setup registry; do not assume a direct key enables every speech route. Credentials: ELEVENLABS_API_KEY. Docs: https://elevenlabs.io/docs/overview/intro - **Runway**: Native Runway video adapter. Credentials: RUNWAY_API_KEY. Docs: https://docs.dev.runwayml.com/ - **Custom endpoint**: Register a compatible endpoint in your standalone deployment. Available only after configuration. Credentials: Adapter URL and server-side authorization. Docs: https://github.com/samsarone/samsar/blob/main/utils/vast-flux2/README.md ## Understand ordering and fallback The documentation begins with hosted Samsar.js. A standalone runtime still uses its validated provider configuration and saved preferences. Native inference may lead a model’s chain; optional media adapters can be reordered where the settings expose that model. 1. Setup writes `runtime/config/available-models.json` from validated providers. 2. **Settings → Model Adapters** lets a standalone administrator reorder available inference, text-to-image and image-to-video adapters. 3. Overrides live in `runtime/config/model-adapter-preferences.json`; rendering config preserves them. 4. A definitive failure can advance a supported fallback chain. An asynchronous job already accepted by a provider stays pinned to that provider while polling. These controls are specific to standalone installations. Hosted routing is managed by Samsar. Adding a provider to the table does not mean it is compatible with every model from that provider. ## Model-specific exceptions | Model or operation | What to know | | --- | --- | | Kimi K3 | Native Kimi first, Samsar alternative; OpenRouter is not in the Kimi chain. Use `kimi-k3` in inference requests; `KIMIK3` is the setup key. | | Qwen inference | Hosted `QWEN3.8` uses OpenRouter internally. Native Alibaba Qwen is a Docker option. | | Qwen Image 3.0 Pro | `QWENIMAGE3PRO` requires the native Alibaba adapter with standard pay-as-you-go credentials. A Token Plan key does not enable this model. | | GPTImage 2.5 | Use keys `GPTIMAGE2.5` and `GPTIMAGE2.5EDIT`. Retired `GPTIMAGE2` and `GPTIMAGE2EDIT` keys are rejected for new requests. Older GenBlaze GPT Image 2 mappings are excluded from these contracts. | | Seedance 2.0 | Provider-billed standalone I2V via fal or the exact validated GenBlaze route; no Samsar adapter in the setup registry. | | Seedance 2.5 | Uses 5, 10 or 15 second 720p scene buckets. Hosted routing and standalone adapter choices differ. | | GMICloud via GenBlaze | Only exact, credential-validated model/operation mappings are exposed. Approximate model matches do not enable a route. | | ElevenLabs speech | The hosted speech contract accepts `ELEVENLABS`; standalone setup advertises speech through fal. Direct ElevenLabs music has its own adapter. | | Local search and recommendations | Local embedding indexes require `OPENAI_API_KEY`; URL crawling also requires `FIRECRAWL_API_KEY`. A Samsar key alone does not satisfy that local implementation. Hosted clients use Samsar.js. | ## Verify before generating Use [Express model discovery](/video#get-videosupported_models) for video workflow allowlists and the [provider capability API](/external-requests#provider-capabilities) for onboarding capabilities. Neither is a live guarantee of every upstream model. Local generated configuration and validated GenBlaze mappings determine what your own deployment can offer. See the [repository provider guide](https://github.com/samsarone/samsar/blob/main/pages/providers-and-models.md) for runtime details and the [model matrix](/models) to compare compatible choices. CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/api.mdx METADATA: title: Video API description: Generate, translate, join, clone, and customize videos through SamsarOne's credit-aware video endpoints. image: img/og/video-api.png sidebar_position: 1 slug: /video sidebar_label: Video API PRIMARY_HEADING: Video API CONTENT_START # Video API Choose scene models in the [video matrix](/models?modality=video), then check this deployment’s Express allowlist below. Start with hosted **samsar-js**; the [adapter guide](/adapters) explains native and standalone choices. Queue a sample video request with your API key and watch status updates without leaving the docs. **Quick facts** - Base URL: `https://api.samsar.one/v1` - Authentication: `Bearer API key` - Execution: `Asynchronous · webhook or status polling` - Billing: `Charged on acceptance; credit headers report usage` - Content type: `application/json` ## Pricing Video generation is duration-based; editing and control routes use simpler flat or no-charge rules. | Operation | Billing rule | | --- | --- | | Text-to-video | `duration_seconds × model_rate` (**20–60 credits/sec**) | | Image-list-to-video | Same formula; narrator avatar adds **4 credits/sec** and express CTA adds **1 credit/sec** | | Translate or join | **3 credits/sec** | | Add or update outro/footer image | **75 credits/request** | | Reroll selected layers | Selected image-generation cost + selected AI-video stage duration | | Clone, subtitles, or cancel | No additional charge | The video model comparison has the current model keys, supported duration units, exact per-second rates, and endpoint rate card. Response credit headers are the final receipt. ## GET /video/supported_models Fetch the currently supported `image_model` and `video_model` keys for `POST /video/text_to_video`. **Request** ```bash curl "https://api.samsar.one/v1/video/supported_models" ``` **Selected response fields (200)** ```json { "IMAGE_MODELS": [ { "label": "GPTImage 2.5", "value": "GPTIMAGE2.5", "basePrice": 46 }, { "label": "NanoBanana Pro", "value": "NANOBANANAPRO", "basePrice": 15 }, { "label": "SEEDREAM", "value": "SEEDREAM", "basePrice": 15 }, { "label": "Wan2.7 Pro", "value": "WAN2.7PRO", "basePrice": 15 } ], "VIDEO_MODELS": [ { "label": "VEO3.1I2V", "value": "VEO3.1I2V", "basePrice": 60, "pricingDistribution": { "pipeline": 4, "inference": 4, "image_gen_edit": 2, "speech": 2, "music": 2, "effects_and_lipsync": 2, "video": 44, "total": 60, "optionalAddons": { "express_cta_generation": 1 } } }, { "label": "VEO3.1I2VFAST", "value": "VEO3.1I2VFAST", "basePrice": 36, "pricingDistribution": { "pipeline": 4, "inference": 4, "image_gen_edit": 2, "speech": 2, "music": 2, "effects_and_lipsync": 2, "video": 20, "total": 36, "optionalAddons": { "express_cta_generation": 1 } } }, { "label": "Nvidia Cosmos 3", "value": "COSMOS3SUPERI2V", "basePrice": 20, "pricingDistribution": { "pipeline": 4, "inference": 4, "image_gen_edit": 2, "speech": 2, "music": 2, "effects_and_lipsync": 2, "video": 4, "total": 20, "optionalAddons": { "express_cta_generation": 1 } } }, { "label": "Seedance 1.5", "value": "SEEDANCEI2V", "basePrice": 30, "pricingDistribution": { "pipeline": 4, "inference": 4, "image_gen_edit": 2, "speech": 2, "music": 2, "effects_and_lipsync": 2, "video": 14, "total": 30, "optionalAddons": { "express_cta_generation": 1 } } }, { "label": "Seedance 2.5 I2V", "value": "SEEDANCE2.5I2V", "basePrice": 50, "pricingDistribution": { "pipeline": 4, "inference": 4, "image_gen_edit": 2, "speech": 2, "music": 2, "effects_and_lipsync": 2, "video": 34, "total": 50, "optionalAddons": { "express_cta_generation": 1 } } }, { "label": "KLINGIMGTOVID3PRO", "value": "KLINGIMGTOVID3PRO", "basePrice": 36, "pricingDistribution": { "pipeline": 4, "inference": 4, "image_gen_edit": 2, "speech": 2, "music": 2, "effects_and_lipsync": 2, "video": 20, "total": 36, "optionalAddons": { "express_cta_generation": 1 } } }, { "label": "Kling 3 Turbo Img2Vid", "value": "KLINGIMGTOVIDTURBO", "basePrice": 36, "pricingDistribution": { "pipeline": 4, "inference": 4, "image_gen_edit": 2, "speech": 2, "music": 2, "effects_and_lipsync": 2, "video": 20, "total": 36, "optionalAddons": { "express_cta_generation": 1 } } }, { "label": "Happy Horse 1.1 I2V", "value": "HAPPYHORSEI2V", "basePrice": 36, "pricingDistribution": { "pipeline": 4, "inference": 4, "image_gen_edit": 2, "speech": 2, "music": 2, "effects_and_lipsync": 2, "video": 20, "total": 36, "optionalAddons": { "express_cta_generation": 1 } } }, { "label": "RUNWAYML", "value": "RUNWAYML", "basePrice": 30, "pricingDistribution": { "pipeline": 4, "inference": 4, "image_gen_edit": 2, "speech": 2, "music": 2, "effects_and_lipsync": 2, "video": 14, "total": 30, "optionalAddons": { "express_cta_generation": 1 } } } ] } ``` - `IMAGE_MODELS` and `VIDEO_MODELS` are generated from the server-side text-to-video allowlist. The arrays shown above are representative and can be filtered by runtime deployment availability. - The full response also includes `IMAGE_EDIT_MODELS`, route-specific `text_to_video` and `image_list_to_video` catalogs, `image_edit_models`, deployment capability data under `deployment` and `available`, `available_providers`, and audio availability. - Each standard priced item includes `label` (display text), `value` (request key), `basePrice` (lowest configured per-unit price for that model), and `pricingDistribution` in credits/sec. `pricingDistribution.optionalAddons.express_cta_generation` is the image-list express CTA surcharge in credits/sec. Custom image-to-video adapters are configured through the top-level custom model configuration, not as a standard `VIDEO_MODELS` item. - Use each item’s `value` directly as `image_model` and `video_model` in `POST /video/text_to_video`. - This endpoint does not require authentication. ## POST /video/text_to_video Submit a prompt-only request to start video generation. Webhooks are optional. **Request body** ```json { "input": { "prompt": "Required text, max 4000 chars", "image_model": "GPTIMAGE2.5 | NANOBANANAPRO | SEEDREAM | WAN2.7PRO", "video_model": "VEO3.1I2V | VEO3.1I2VFAST | COSMOS3SUPERI2V | SEEDANCEI2V | SEEDANCE2.5I2V | KLINGIMGTOVID3PRO | KLINGIMGTOVIDTURBO | HAPPYHORSEI2V | RUNWAYML", "duration": 30, "tone": "grounded | cinematic", "aspect_ratio": "16:9 | 9:16", "language": "auto | en | es | fr | ja | th | zh | bn | hi | sa | la", "backingtrack_model": "LYRIA3 | ELEVENLABS_MUSIC", "tts_model": "ELEVENLABS | OPENAI | GOOGLE", "speakerOptions": { "openAISpeakers": ["nova"] }, "inference_model": "gpt-6-astra | gemini-3.1-pro | QWEN3.8 | kimi-k3", "enable_subtitles": true, "font_key": "Poppins", "outro_image_url": "https://cdn.example.com/outro.png", "generate_outro_image": false, "cta_url": "https://example.com/book", "cta_text_top": "Scan to book", "cta_text_bottom": "Limited availability", "cta_logo": "https://cdn.example.com/logo-white.png", "outro_cta_image": { "top_text": "Shop the drop", "middle_image": { "url": "https://cdn.example.com/drop-logo.png" }, "bottom_text": "Limited availability" }, "add_footer_animation": true, "footer_metadata": [ { "url": "https://example.com/book", "title": "Book now" } ] }, "webhookUrl": "https://example.com/webhook" } ``` - `prompt`, `image_model`, `video_model`, `duration` are required; `duration` ≤ 240 seconds. - `language` is optional; pass `auto` (default) or one of the listed language codes. - `backingtrack_model` optional; selects the express backing track generator. Use `LYRIA3` for Google Lyria 3 Pro native music generation or `ELEVENLABS_MUSIC` for ElevenLabs Music. Compatibility aliases include `backing_track_model`, `backingTrackModel`, `music_provider`, and `musicProvider`; legacy `LYRIA2` requests are treated as `LYRIA3`. - `tts_model` optional; selects the speaker library used for text-to-speech assignment. Use `ELEVENLABS`, `OPENAI`, or `GOOGLE`. Compatibility aliases include `ttsModel`, `tts_provider`, and `ttsProvider`. When omitted, Samsar uses the default speaker assignment behavior. - `speakerOptions` optional; compatibility alias: `speaker_options`. Use it with `tts_model` to constrain assignment to `openAISpeakers`, `elevenLabsSpeakers`, or `googleSpeakers`. When provided, request speaker options override account speaker settings for that request. Google TTS requests should include `googleSpeakerDetails` entries with `value` or `voiceId`, `languageCode` or `languageCodes`, and gender metadata such as `Gender` or `ssmlGender`. - `inference_model` optional; selects the render-planning model. Supported values are `gpt-6-astra`, `gemini-3.1-pro`, `QWEN3.8`, and `kimi-k3`. `QWEN3.8` uses Qwen 3.8 Max for text and vision. Compatibility alias: `inferenceModel`. When omitted, Samsar uses the account default (`gpt-6-astra`). - `enable_subtitles` optional; defaults to `true`. Set to `false` to skip subtitle generation entirely. - `font_key` optional; must be supported for the chosen `language`. It sets both subtitle and speaker fonts. Use `GET /supported_fonts` or [/fonts](/fonts) to preview choices. - Provided outro image mode: set `outro_image_url` to append a custom outro frame. `add_outro_animation` is optional; when true, it applies a fade overlay during the outro. `add_outro_focus_area` requires `add_outro_animation: true` and a valid `outro_focust_area`. - Generated outro image mode: set `generate_outro_image: true` and provide either `cta_url` for a QR center image or `outro_cta_image` for a supplied center logo/CTA image. `outro_cta_image` uses `{ "top_text", "middle_image", "bottom_text" }`; `middle_image` accepts a public URL (`{ "url" }`, `{ "image_url" }`) or image data (`{ "data_url" }`, `{ "image_data", "mime_type" }`). Top-level `cta_text_top` and `cta_text_bottom` remain supported for generated outros. This mode automatically enables `add_outro_animation`, `add_outro_focus_area`, and computes `outro_focust_area`. - Footer QR mode: set `add_footer_animation: true` and provide `footer_metadata` with `{ "url", "title" }` items. A single item is applied to every generated scene; multiple items map by generated scene index. The frame processor renders the bottom CTA section only on layers where this option is enabled. - Use only one outro mode in a request. If `generate_outro_image` is true, omit `outro_image_url`, `add_outro_animation`, `add_outro_focus_area`, and `outro_focust_area` because Samsar derives those values. - `outro_focust_area` / `outro_focus_area` values are `{ x, y, width, height }` within the output canvas (`16:9` is `1792x1024`, `9:16` is `1024x1792`). - `video_model_sub_type` is not part of the request shape; describe style direction in `prompt` and `tone`. - Request bodies can be nested under `input` (recommended) or sent as a flat JSON payload. ### Seedance 2.5 scene behavior `SEEDANCE2.5I2V` is a frame-to-video model used for the scene animation stage of both text-to-video and image-list-to-video. Samsar first applies its normal narrative, scene, and connected-audio timing. Each scene is then assigned the smallest supported Seedance 2.5 duration that can contain that timing: `5`, `10`, or `15` seconds. For example, connected audio durations of 4, 8, and 14 seconds produce 5-, 10-, and 15-second scene renders. Seedance 2.5 requests render at `720p`. Fal receives the selected `aspect_ratio`; GMICloud through GenBlaze sends `ratio: "adaptive"` for framed generation so the already-sized first frame determines the output shape. `generate_audio` is enabled only when the AI-video layer type is `sound_effect`; narration, music, and ordinary scene audio continue through Samsar's separate audio stages. Standalone Docker can expose GMICloud, Fal, and Samsar-js adapters for this model. One adapter is selected per request from the enabled order saved in **Settings → Model Adapters**; the same layer request is never submitted to multiple adapters. Hosted production and hosted external Seedance 2.5 requests are pinned to the validated GMICloud route `seedance-2-5-260628` without a Fal fallback. **Sample request** ```bash curl -X POST https://api.samsar.one/v1/video/text_to_video \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "prompt": "A timelapse of clouds over a futuristic city at sunrise", "duration": 30, "image_model": "GPTIMAGE2.5", "video_model": "RUNWAYML", "tone": "grounded", "aspect_ratio": "16:9", "language": "en", "enable_subtitles": true, "font_key": "Poppins", "generate_outro_image": true, "cta_url": "https://example.com/tour", "cta_text_top": "Scan for the tour", "cta_text_bottom": "Book a demo", "add_footer_animation": true, "footer_metadata": [ { "url": "https://example.com/tour", "title": "Book a demo" } ] } }' ``` **Sample request: generate CTA image outro with a center logo** ```bash curl -X POST https://api.samsar.one/v1/video/text_to_video \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "prompt": "A cinematic launch teaser for a new product line", "duration": 20, "image_model": "GPTIMAGE2.5", "video_model": "RUNWAYML", "aspect_ratio": "9:16", "generate_outro_image": true, "outro_cta_image": { "top_text": "Shop the drop", "middle_image": { "url": "https://cdn.example.com/drop-logo.png" }, "bottom_text": "Limited availability" } } }' ``` **Success response (200)** ```json { "request_id": "vid_a1b2c3d4", "session_id": "vid_a1b2c3d4" } ``` **Error responses** - `400` invalid or missing fields (empty prompt, unsupported models, duration > 240, etc.) - `401` invalid API key ## POST /video/image_list_to_video Create a video from an ordered list of images. Samsar builds a narrative from the images and optional metadata, then renders the video using `VEO3.1I2V`, `VEO3.1I2VFAST`, `COSMOS3SUPERI2V`, `SEEDANCEI2V`, `SEEDANCE2.5I2V`, `KLINGIMGTOVID3PRO`, `KLINGIMGTOVIDTURBO`, `HAPPYHORSEI2V`, or `RUNWAYML`. If `video_model` is omitted, it defaults to `RUNWAYML`. **Request body** ```json { "input": { "image_urls": [ "https://cdn.example.com/frame1.png", "https://cdn.example.com/frame2.png" ], "prompt": "Optional high-level direction", "metadata": { "project": "launch_trailer" }, "video_model": "VEO3.1I2V | VEO3.1I2VFAST | COSMOS3SUPERI2V | SEEDANCEI2V | SEEDANCE2.5I2V | KLINGIMGTOVID3PRO | KLINGIMGTOVIDTURBO | HAPPYHORSEI2V | RUNWAYML", "image_model": "GPTIMAGE2.5 | NANOBANANAPRO | SEEDREAM | WAN2.7PRO", "aspect_ratio": "16:9 | 9:16", "language": "auto | en | es | fr | ja | th | zh | bn | hi | sa | la", "backingtrack_model": "LYRIA3 | ELEVENLABS_MUSIC", "tts_model": "ELEVENLABS | OPENAI | GOOGLE", "speakerOptions": { "openAISpeakers": ["nova"] }, "inference_model": "gpt-6-astra | gemini-3.1-pro | QWEN3.8 | kimi-k3", "enable_subtitles": true, "font_key": "Poppins", "limit_single_narrator": false, "add_narrator_avatar": false, "generate_outro_image": false, "express_cta_generation": false, "cta_url": "https://example.com/book", "cta_text_top": "Scan to book", "cta_text_bottom": "Limited availability", "cta_logo": "https://cdn.example.com/logo-white.png", "outro_cta_image": { "top_text": "Shop the drop", "middle_image": { "url": "https://cdn.example.com/drop-logo.png" }, "bottom_text": "Limited availability" }, "add_footer_animation": false, "footer_metadata": [ { "url": "https://example.com/blue-lagoon", "title": "Blue Lagoon Tour" } ] }, "webhookUrl": "https://example.com/webhook" } ``` - `image_urls` required; non-empty array of URL strings or objects containing `image_url`, `imageUrl`, `url`, `src`, `enhanced_url`, or `enhancedUrl`. Object entries may also include `title`, `image_text`, `activity_title`, `name`, or `label` for narration context. - `metadata` optional plain object forwarded into the narrative builder. - `prompt` optional text used to guide pacing/tone. - `video_model` optional; supported values are `VEO3.1I2V`, `VEO3.1I2VFAST`, `COSMOS3SUPERI2V`, `SEEDANCEI2V`, `SEEDANCE2.5I2V`, `KLINGIMGTOVID3PRO`, `KLINGIMGTOVIDTURBO`, `HAPPYHORSEI2V`, and `RUNWAYML`. Defaults to `RUNWAYML`. - `aspect_ratio` optional; supports `16:9` and `9:16`. Omitted or unsupported values fall back to `16:9`. - `language` optional; defaults to `auto` for detection. - `backingtrack_model` optional; selects the express backing track generator. Use `LYRIA3` for Google Lyria 3 Pro native music generation or `ELEVENLABS_MUSIC` for ElevenLabs Music. Compatibility aliases include `backing_track_model`, `backingTrackModel`, `music_provider`, and `musicProvider`; legacy `LYRIA2` requests are treated as `LYRIA3`. - `tts_model` optional; selects the speaker library used for text-to-speech assignment. Use `ELEVENLABS`, `OPENAI`, or `GOOGLE`. Compatibility aliases include `ttsModel`, `tts_provider`, and `ttsProvider`. When omitted, Samsar uses the default speaker assignment behavior. - `speakerOptions` optional; compatibility alias: `speaker_options`. Use it with `tts_model` to constrain assignment to `openAISpeakers`, `elevenLabsSpeakers`, or `googleSpeakers`. When provided, request speaker options override account speaker settings for that request. Google TTS requests should include `googleSpeakerDetails` entries with `value` or `voiceId`, `languageCode` or `languageCodes`, and gender metadata such as `Gender` or `ssmlGender`. - `inference_model` optional; selects the render-planning model. Supported values are `gpt-6-astra`, `gemini-3.1-pro`, `QWEN3.8`, and `kimi-k3`. `QWEN3.8` uses Qwen 3.8 Max for text and vision. Compatibility alias: `inferenceModel`. When omitted, Samsar uses the account default (`gpt-6-astra`). - `enable_subtitles` optional; defaults to `true`. Set to `false` to skip subtitle generation entirely. - `font_key` optional; must be supported for the chosen `language`. It sets both subtitle and speaker fonts. - `limit_single_narrator` optional boolean; defaults to `false`. When true, Samsar keeps all narration under one narrator identity. - `add_narrator_avatar` optional boolean; defaults to `false`. When true, Samsar automatically enables `limit_single_narrator`, generates an influencer-style human narrator avatar suitable for the image-list narrative, and overlays the avatar video bottom-center or centered in the footer row when footer metadata is present. - Provided outro image mode: set `outro_image_url` to append a custom outro frame. `add_outro_animation` is optional; when true, it applies a fade-to-black overlay during the outro. `add_outro_focus_area` requires `add_outro_animation: true` and a valid `outro_focust_area`. - Generated outro image mode: set `generate_outro_image: true` and provide either `cta_url` for a QR center image or `outro_cta_image` for a supplied center logo/CTA image. `outro_cta_image` uses `{ "top_text", "middle_image", "bottom_text" }`; `middle_image` accepts a public URL (`{ "url" }`, `{ "image_url" }`) or image data (`{ "data_url" }`, `{ "image_data", "mime_type" }`). Top-level `cta_text_top` and `cta_text_bottom` remain supported for generated outros. This mode automatically enables `add_outro_animation`, `add_outro_focus_area`, and computes `outro_focust_area`. - Footer QR mode: set `add_footer_animation: true` and provide `footer_metadata` with one `{ "url", "title" }` item per image scene. Samsar generates one QR image per scene in the frame processor and renders a footer section on every frame with the title on the left and QR code on the right. - Express CTA generation mode: set `express_cta_generation: true` with one `cta_url` and omit `cta_text_top`, `cta_text_bottom`, and `footer_metadata`. Samsar generates concise per-scene footer CTA titles from the image scene payload and clean outro CTA text from the session context, then normalizes the request into the same generated-outro and footer payload shape before rendering. Compatibility aliases include `expressCtaGeneration`, `auto_generate_cta_text`, `autoGenerateCtaText`, `generate_cta_texts`, and `generateCtaTexts`. - Use only one outro mode in a request. If `generate_outro_image` or `express_cta_generation` is true, omit `outro_image_url`, `add_outro_animation`, `add_outro_focus_area`, and `outro_focust_area` because Samsar derives those values. - `outro_focust_area` / `outro_focus_area` values are `{ x, y, width, height }` within the output canvas (`16:9` is `1792x1024`, `9:16` is `1024x1792`). - `video_model_sub_type` is not part of the request shape; describe style direction in `prompt`. - Billing uses the selected model rate × final duration. Narrator avatar generation adds **4 credits/sec** and express CTA generation adds **1 credit/sec**; see the central video rate table for exact model prices. **Sample request: provided outro image** ```bash curl -X POST https://api.samsar.one/v1/video/image_list_to_video \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "image_urls": [ "https://cdn.example.com/frame1.png", "https://cdn.example.com/frame2.png" ], "prompt": "Make this feel like a fast-paced teaser", "metadata": { "project": "launch_trailer" }, "video_model": "KLINGIMGTOVID3PRO", "aspect_ratio": "16:9", "language": "en", "enable_subtitles": true, "font_key": "Poppins", "limit_single_narrator": true, "outro_image_url": "https://cdn.example.com/outro.png", "add_outro_animation": true, "add_outro_focus_area": true, "outro_focust_area": { "x": 240, "y": 140, "width": 720, "height": 420 } }, "webhookUrl": "https://example.com/video-webhook" }' ``` **Sample request: generate QR outro image server-side** ```bash curl -X POST https://api.samsar.one/v1/video/image_list_to_video \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "image_urls": [ { "image_url": "https://cdn.example.com/frame1.png", "title": "Blue Lagoon Tour" }, { "image_url": "https://cdn.example.com/frame2.png", "title": "Sunset Dinner" } ], "prompt": "Travel offer reel with a scannable booking outro", "metadata": { "project": "destination_campaign" }, "video_model": "RUNWAYML", "aspect_ratio": "9:16", "language": "en", "enable_subtitles": true, "font_key": "Poppins", "add_narrator_avatar": true, "generate_outro_image": true, "cta_url": "https://example.com/book", "cta_text_top": "Scan to book", "cta_text_bottom": "Limited availability", "cta_logo": "https://cdn.example.com/logo-white.png", "add_footer_animation": true, "footer_metadata": [ { "url": "https://example.com/blue-lagoon", "title": "Blue Lagoon Tour" }, { "url": "https://example.com/sunset-dinner", "title": "Sunset Dinner" } ] }, "webhookUrl": "https://example.com/video-webhook" }' ``` **Sample request: generate CTA image outro with a center logo** ```bash curl -X POST https://api.samsar.one/v1/video/image_list_to_video \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "image_urls": [ "https://cdn.example.com/frame1.png", "https://cdn.example.com/frame2.png" ], "prompt": "Product launch teaser with a branded CTA outro", "video_model": "RUNWAYML", "aspect_ratio": "9:16", "generate_outro_image": true, "outro_cta_image": { "top_text": "Shop the drop", "middle_image": { "url": "https://cdn.example.com/drop-logo.png" }, "bottom_text": "Limited availability" } }, "webhookUrl": "https://example.com/video-webhook" }' ``` **Sample request: express CTA generation from one link** ```bash curl -X POST https://api.samsar.one/v1/video/image_list_to_video \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "image_urls": [ { "image_url": "https://cdn.example.com/frame1.png", "title": "Blue Lagoon Tour" }, { "image_url": "https://cdn.example.com/frame2.png", "title": "Sunset Dinner" } ], "prompt": "Travel offer reel with concise scene CTAs", "metadata": { "brand": "Guidestination", "offer": "Bookable Bangkok experiences" }, "video_model": "RUNWAYML", "aspect_ratio": "9:16", "language": "en", "enable_subtitles": true, "font_key": "Poppins", "express_cta_generation": true, "cta_url": "https://example.com/book" }, "webhookUrl": "https://example.com/video-webhook" }' ``` **Success response (200)** ```json { "request_id": "vid_img_a1b2c3", "session_id": "vid_img_a1b2c3" } ``` **Error responses** - `400` invalid or missing `image_urls`. - `401` invalid API key. ## POST /external_users/text_to_video and /external_users/image_list_to_video External-user video creation routes accept the same `input` parameters as `/video/text_to_video` and `/video/image_list_to_video`, including generated CTA outros (`generate_outro_image` with `cta_url` for a QR center image or `outro_cta_image` for a supplied center logo/CTA image), image-list express CTA generation (`express_cta_generation` with `cta_url`), provided outro images, footer QR sections (`add_footer_animation`, `footer_metadata`), subtitle/font options, optional `speakerOptions`, optional `inference_model`, and image-list `video_model` / `aspect_ratio`, `limit_single_narrator`, and `add_narrator_avatar`. Wrap the request with an `external_user` object. Responses return an external request/session id; either the returned `extreq_...` id or the normalized external id can be used with `/external_users/status`, `/external_users/add_outro_image`, `/external_users/update_outro_image`, `/external_users/update_footer_image`, `/external_users/translate_video`, and `/external_users/join_videos`. External user records store a stable `unique_key`. If `unique_key` is not provided, Samsar uses `external_user_id` as the key. V2 clients can create and later reference an external user through `POST /v2/user/create_external_user`. Repeated video routes also perform server-side compatibility routing. If a main `/video/*` route receives an external request id, normalized external id, or external-owned upstream session id, it is processed through the external-user billing path. If an `/external_users/*` route receives a normal internal `GlobalSession`/video session id owned by the authenticated account and no external user context is supplied, it is processed through the internal billing path. ```json { "external_user": { "provider": "guidestination", "unique_key": "traveler_123", "external_user_id": "traveler_123" }, "input": { "image_urls": [ { "image_url": "https://cdn.example.com/frame1.png", "title": "Blue Lagoon Tour" } ], "video_model": "RUNWAYML", "generate_outro_image": true, "cta_url": "https://example.com/book", "add_footer_animation": true, "footer_metadata": [ { "url": "https://example.com/book", "title": "Book now" } ] }, "webhookUrl": "https://example.com/webhook" } ``` ## POST /video/add_outro_image Clone an existing session, then add or replace the outro image in the cloned session and queue frame/video generation. Use a provided full outro image, or ask Samsar to generate the outro from existing session image layers with either a QR center image or supplied center logo/CTA image. **Request body** ```json { "input": { "videoSessionId": "vid_a1b2c3d4", "outro_image_url": "https://cdn.example.com/outro.png", "add_outro_animation": true, "add_outro_focus_area": true, "outro_focust_area": { "x": 240, "y": 140, "width": 720, "height": 420 } }, "webhookUrl": "https://example.com/webhook" } ``` - `videoSessionId` required; must belong to the API key. - `outro_image_url` required unless `generate_outro_image` is true; URL (or data URL) for a full outro image. - `generate_outro_image` optional boolean; when true, provide either `cta_url` or `outro_cta_image` and omit `outro_image_url`. - `cta_url` optional for generated outro mode; when provided, it is converted into a scannable QR code in the center image area. - `outro_cta_image` optional for generated outro mode; uses `{ "top_text", "middle_image", "bottom_text" }` and places `middle_image` directly in the same center area used by QR outros, resized without changing aspect ratio. - `add_outro_animation` optional boolean; when true, adds a fade-to-black overlay in the final outro layer. - `add_outro_focus_area` optional boolean; requires `add_outro_animation` and valid `outro_focust_area`. - `outro_focust_area` optional; `{ x, y, width, height }`, validated against the session canvas. - Behavior: - If the last layer is already the outro, it replaces that outro. - If the last layer is not an outro, it appends a new outro layer. - In both cases, a `0.5s` fade-in transition of the outro image is applied to the end of the prior layer so it leads into the outro start frame. - Credits are billed at **75 credits per request**. **Sample request** ```bash curl -X POST https://api.samsar.one/v1/video/add_outro_image \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "videoSessionId": "vid_a1b2c3d4", "outro_image_url": "https://cdn.example.com/outro.png", "add_outro_animation": true, "add_outro_focus_area": true, "outro_focust_area": { "x": 240, "y": 140, "width": 720, "height": 420 } }, "webhookUrl": "https://example.com/video-webhook" }' ``` **Sample request: add generated CTA image outro** ```bash curl -X POST https://api.samsar.one/v1/video/add_outro_image \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "videoSessionId": "vid_a1b2c3d4", "generate_outro_image": true, "outro_cta_image": { "top_text": "Shop the drop", "middle_image": { "url": "https://cdn.example.com/drop-logo.png" }, "bottom_text": "Limited availability" } }, "webhookUrl": "https://example.com/video-webhook" }' ``` **Success response (200)** ```json { "request_id": "vid_outro_a1b2c3", "session_id": "vid_outro_a1b2c3" } ``` **Error responses** - `400` invalid or missing fields. - `401` invalid API key. - `402` insufficient credits. - `404` session not found. ## POST /video/update_outro_image Clone an existing session that already has an outro layer, update that outro layer, and queue only the frame/video regeneration stages. This route does not rerun prompt, image, speech, music, or AI video generation. Use one of two modes: - Provided outro image mode: pass `outro_image_url` (or `outroImageUrl`, `new_outro_image_url`, `newOutroImageUrl`) as an HTTP(S) URL or image data URL. - Generated CTA outro mode: pass `generate_outro_image: true` with either `cta_url` or `outro_cta_image`, or provide `cta_url` / `outro_cta_image` without an `outro_image_url`. With `cta_url`, Samsar builds a QR-code outro. With `outro_cta_image`, Samsar places the supplied logo/CTA image in the same center area, resized without changing aspect ratio. In both cases it derives the focus area server-side and keeps optional top/bottom CTA text above the fade overlay. **Provided outro image request body** ```json { "input": { "videoSessionId": "vid_a1b2c3d4", "outro_image_url": "https://cdn.example.com/outro.png", "add_outro_animation": true, "add_outro_focus_area": true, "outro_focust_area": { "x": 240, "y": 140, "width": 720, "height": 420 } }, "webhookUrl": "https://example.com/webhook" } ``` **Generated CTA outro request body** ```json { "input": { "videoSessionId": "vid_a1b2c3d4", "generate_outro_image": true, "cta_url": "https://example.com/book", "cta_text_top": "Scan to book", "cta_text_bottom": "Limited availability", "add_outro_animation": true }, "webhookUrl": "https://example.com/webhook" } ``` **Generated CTA image outro request body** ```json { "input": { "videoSessionId": "vid_a1b2c3d4", "generate_outro_image": true, "outro_cta_image": { "top_text": "Shop the drop", "middle_image": { "url": "https://cdn.example.com/drop-logo.png" }, "bottom_text": "Limited availability" } }, "webhookUrl": "https://example.com/webhook" } ``` - `videoSessionId` required; must belong to the API key. - `outro_image_url` required unless `generate_outro_image` is true. - `generate_outro_image` optional boolean; when true, `cta_url` or `outro_cta_image` is required and `outro_image_url` must be omitted. If `cta_url` or `outro_cta_image` is provided without `outro_image_url`, generated CTA outro mode is inferred. - `cta_url` optional for generated CTA outro mode; must be HTTP(S). It is converted into a scannable QR code. - `outro_cta_image` optional for generated CTA outro mode; uses `{ "top_text", "middle_image", "bottom_text" }`. `middle_image` accepts a public URL (`{ "url" }`, `{ "image_url" }`) or image data (`{ "data_url" }`, `{ "image_data", "mime_type" }`). - `cta_text_top` optional; rendered near the top of the outro layer above the fade overlay. - `cta_text_bottom` optional; rendered near the bottom of the outro layer above the fade overlay. - `cta_logo` optional and accepted for compatibility with QR generated-outro requests. - `add_outro_animation` optional boolean. In generated CTA outro mode it defaults to true; when true, Samsar adds a black overlay that fades from 60% opacity to 0% while the generated center image and CTA text stay above it. - `add_outro_focus_area` optional for provided outro image mode; requires `add_outro_animation` and valid `outro_focust_area`. Generated CTA outro mode calculates the focus area server-side. - Credits are billed at **75 credits per request**. **Sample request: update with generated CTA outro** ```bash curl -X POST https://api.samsar.one/v1/video/update_outro_image \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "videoSessionId": "vid_a1b2c3d4", "generate_outro_image": true, "cta_url": "https://example.com/book", "cta_text_top": "Scan to book", "cta_text_bottom": "Limited availability" }, "webhookUrl": "https://example.com/video-webhook" }' ``` **Success response (200)** ```json { "request_id": "vid_outro_update_a1b2c3", "session_id": "vid_outro_update_a1b2c3" } ``` ## POST /video/update_footer_image Clone an existing video session, update or remove the scene footer CTA, and queue only footer image creation when needed plus frame/video regeneration. This route does not rerun prompt, image, speech, music, or AI video generation. Use `remove_footer: true` to remove the footer from all scenes in the cloned rerender. Otherwise provide at least one CTA field and Samsar applies the same minimal footer layout to each scene. **Update footer request body** ```json { "input": { "videoSessionId": "vid_a1b2c3d4", "cta_text": "Scan to book", "cta_logo": "https://cdn.example.com/logo-white.png", "cta_url": "https://example.com/book" }, "webhookUrl": "https://example.com/webhook" } ``` **Remove footer request body** ```json { "input": { "videoSessionId": "vid_a1b2c3d4", "remove_footer": true }, "webhookUrl": "https://example.com/webhook" } ``` - `videoSessionId` required; must belong to the API key. - `remove_footer` optional boolean; defaults to false. When true, `cta_text`, `cta_logo`, and `cta_url` are ignored and all scene footer metadata is cleared. - `cta_text` optional string; rendered in the bottom-left footer area. - `cta_logo` optional HTTP(S) URL or image data URL; rendered below the CTA text in the bottom-left footer area. - `cta_url` optional HTTP(S) URL; converted into a scannable QR code on the right side of the footer. - At least one of `cta_text`, `cta_logo`, or `cta_url` is required unless `remove_footer` is true. - Credits are billed at **75 credits per request**. **Sample request** ```bash curl -X POST https://api.samsar.one/v1/video/update_footer_image \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "videoSessionId": "vid_a1b2c3d4", "cta_text": "Scan to book", "cta_logo": "https://cdn.example.com/logo-white.png", "cta_url": "https://example.com/book" }, "webhookUrl": "https://example.com/video-webhook" }' ``` **Success response (200)** ```json { "request_id": "vid_footer_update_a1b2c3", "session_id": "vid_footer_update_a1b2c3" } ``` ## POST /external_users/add_outro_image External-user equivalent of `/video/add_outro_image`. Pass the external user identity and an external request/session id such as `extreq_df04419ab0cf426e9b30891da71a9293`; Samsar resolves it to the upstream video session, charges the external user's credits, clones the session, and queues only frame/video regeneration. **Request body** ```json { "external_user": { "provider": "guidestination", "external_user_id": "traveler_123" }, "input": { "request_id": "extreq_df04419ab0cf426e9b30891da71a9293", "outro_image_url": "https://cdn.example.com/outro.png", "add_outro_animation": true, "add_outro_focus_area": true, "outro_focust_area": { "x": 240, "y": 140, "width": 720, "height": 420 } }, "webhookUrl": "https://example.com/webhook" } ``` - `request_id` / `session_id` required; use the external request id returned by `/external_users/text_to_video` or `/external_users/image_list_to_video`. - Supports the same provided-outro and generated CTA outro modes as `/video/add_outro_image`. - Generated CTA outro mode accepts `cta_url` for a QR center image or `outro_cta_image` for a supplied center logo/CTA image. - `add_outro_animation`, `add_outro_focus_area`, and `outro_focust_area` match `/video/add_outro_image`. - Credits are billed at **75 credits per request** against the external user. ## POST /external_users/update_outro_image External-user equivalent of `/video/update_outro_image`. Use this endpoint when the source video was created through `/external_users/text_to_video` or `/external_users/image_list_to_video`; pass either the returned `extreq_...` id or the normalized external id. **Generated CTA outro request body** ```json { "external_user": { "provider": "guidestination", "external_user_id": "traveler_123" }, "input": { "request_id": "extreq_df04419ab0cf426e9b30891da71a9293", "generate_outro_image": true, "outro_cta_image": { "top_text": "Shop the drop", "middle_image": { "url": "https://cdn.example.com/drop-logo.png" }, "bottom_text": "Limited availability" }, "add_outro_animation": true }, "webhookUrl": "https://example.com/webhook" } ``` - `request_id` / `session_id` required; use the external request id returned by an external-user video route. - Supports the same provided-outro and generated CTA outro modes as `/video/update_outro_image`. - Generated CTA outro mode accepts `cta_url` for a QR center image or `outro_cta_image` for a supplied center logo/CTA image, then derives `add_outro_focus_area` and `outro_focust_area` server-side. - Credits are billed at **75 credits per request** against the external user. **Success response (200)** ```json { "request_id": "extreq_9b30891da71a9293df04419ab0cf426e", "session_id": "extreq_9b30891da71a9293df04419ab0cf426e", "external_request_id": "extreq_9b30891da71a9293df04419ab0cf426e", "status_endpoint": "/v1/external_users/status?request_id=extreq_9b30891da71a9293df04419ab0cf426e", "creditsCharged": 75 } ``` ## POST /external_users/update_footer_image External-user equivalent of `/video/update_footer_image`. Use this endpoint when the source video was created through `/external_users/text_to_video` or `/external_users/image_list_to_video`; pass either the returned `extreq_...` id or the normalized external id. **Request body** ```json { "external_user": { "provider": "guidestination", "external_user_id": "traveler_123" }, "input": { "request_id": "extreq_df04419ab0cf426e9b30891da71a9293", "cta_text": "Scan to book", "cta_logo": "https://cdn.example.com/logo-white.png", "cta_url": "https://example.com/book" }, "webhookUrl": "https://example.com/webhook" } ``` - `request_id` / `session_id` required; use the external request id returned by an external-user video route. - Supports the same `remove_footer`, `cta_text`, `cta_logo`, and `cta_url` fields as `/video/update_footer_image`. - Credits are billed at **75 credits per request** against the external user. **Success response (200)** ```json { "request_id": "extreq_9b30891da71a9293df04419ab0cf426e", "session_id": "extreq_9b30891da71a9293df04419ab0cf426e", "external_request_id": "extreq_9b30891da71a9293df04419ab0cf426e", "status_endpoint": "/v1/external_users/status?request_id=extreq_9b30891da71a9293df04419ab0cf426e", "creditsCharged": 75 } ``` ## POST /video/clone Deep-copy a completed video session into a new session and queue only the final video render stage. Use this when you need a distinct session id and a distinct rendered video URL, but the same timeline, resources, audio, overlays, subtitle settings, footer/outro metadata, and generated assets. `POST /v2/video/clone` exposes the same operation on the V2 route surface. **Request body** ```json { "input": { "videoSessionId": "vid_a1b2c3d4" }, "webhookUrl": "https://example.com/webhook" } ``` - `videoSessionId` required; must be a completed video session that belongs to the authenticated user. - The clone copies the source session data and local generated resources into the new session. - Upstream generation stages are marked complete; only `video_generation` is queued so the renderer creates a new `video/output/video-_*.mp4` file and new CDN URL. - This endpoint currently does not charge additional credits. **Sample request** ```bash curl -X POST https://api.samsar.one/v1/video/clone \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "videoSessionId": "vid_a1b2c3d4" }, "webhookUrl": "https://example.com/video-webhook" }' ``` **Success response (200)** ```json { "request_id": "vid_clone_a1b2c3", "session_id": "vid_clone_a1b2c3", "status": "PENDING", "type": "video", "creditsCharged": 0 } ``` Poll `GET /status?request_id=` or `GET /v2/status?request_id=` until the standard video status response returns `status: "COMPLETED"` with `result_url`, `result_urls`, `has_subtitles`, and `result_language`. **Error responses** - `400` invalid/missing fields or source session has no completed video URL - `401` invalid API key - `404` session not found ## POST /video/reroll-layers Reroll selected 1-based layers in an owned completed video session. The selected layers restart at image generation, then AI-video and lip-sync/sound-effect work runs as needed for those layers. Frame generation, generated outro tiles when present, and final video generation rerun for the whole session. `POST /v2/reroll-layers` and `POST /v2/video/reroll-layers` expose the same operation on the V2 route surface. **Request body** ```json { "input": { "videoSessionId": "vid_a1b2c3d4", "layer_indexes": [3, 6, 11] }, "webhookUrl": "https://example.com/webhook" } ``` - `videoSessionId` required; must be a video session that belongs to the authenticated user and is not currently rendering. - `layer_indexes` required; accepts positive 1-based layer indexes. - The route updates the same session in place and does not clone the video. - Quote-only requests with `quote_only: true` return the credit estimate without charging or queuing work. **Quote-only request** ```json { "input": { "videoSessionId": "vid_a1b2c3d4", "layer_indexes": [3, 6], "quote_only": true } } ``` **Success response (200)** ```json { "request_id": "vid_a1b2c3d4", "session_id": "vid_a1b2c3d4", "status": "PENDING", "layer_indexes": [3, 6, 11], "creditQuote": { "layerCount": 3, "totalCredits": 164, "imageCredits": 92, "aiVideoCredits": 72 }, "creditsCharged": 164 } ``` **Error responses** - `400` invalid/missing fields, unsupported layer indexes, or missing image prompts - `401` invalid API key - `402` insufficient credits - `404` session not found - `409` session is currently rendering or paused ## POST /video/translate_video Translate an existing video session into a new language. This creates a new session and runs only the required stages (speech translation, lip sync, optional transcription, final render) to produce a translated video. `POST /video/retranslate_video` is available as an alias for the same operation. **Request body** ```json { "input": { "videoSessionId": "vid_a1b2c3d4", "language": "en | es | fr | ja | th | zh | bn | hi | sa | la", "enable_subtitles": false, "translate_outro": true, "translate_footer": true }, "webhookUrl": "https://example.com/webhook" } ``` - `videoSessionId` required; must be a video session that belongs to the API key. - `language` required; pass a supported language code. - `enable_subtitles` optional boolean; defaults to `false`. When `true`, Samsar generates translated transcript/subtitle text for the cloned session. - `translate_outro` optional boolean; defaults to `true`. When enabled, stored generated-outro top and bottom CTA text are translated and reused in the cloned outro. - `translate_footer` optional boolean; defaults to `true`. When enabled, stored footer CTA text is translated per footer-enabled layer and reused in the cloned footer overlay. - `outro_image_url` is no longer part of the translate payload. Use `POST /video/update_outro_image` before or after translation when the outro image itself needs to change. - Credits are billed at **3 credits/sec** using the duration of the source session. **Sample request** ```bash curl -X POST https://api.samsar.one/v1/video/translate_video \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "videoSessionId": "vid_a1b2c3d4", "language": "es", "enable_subtitles": false, "translate_outro": true, "translate_footer": true }, "webhookUrl": "https://example.com/video-webhook" }' ``` **Success response (200)** ```json { "request_id": "vid_tr_a1b2c3", "session_id": "vid_tr_a1b2c3" } ``` **Error responses** - `400` invalid/missing fields (unknown session id, unsupported language, etc.) - `401` invalid API key - `402` insufficient credits ## POST /video/join_videos Join multiple completed video sessions into a single session by appending their timelines in order. This creates a new session and queues transcription, frame generation, and final render to produce the joined video. **Request body** ```json { "input": { "session_ids": ["vid_a1b2c3d4", "vid_e5f6g7h8"], "blend_scenes": true }, "webhookUrl": "https://example.com/webhook" } ``` - `session_ids` required; array of **2+** unique video session ids. - `blend_scenes` optional boolean (default `false`). When `true`, joins apply scene-boundary blending: previous outro image is carried into the next scene with a 1s fade-out, and background music layers are adjusted around boundaries (`+0.5s`) for smoother transitions. - All sessions must belong to the API key, and must have the same `aspect_ratio` and `framesPerSecond`. - Credits are billed at **3 credits/sec** using the joined duration (sum of session durations). **Sample request** ```bash curl -X POST https://api.samsar.one/v1/video/join_videos \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "session_ids": ["vid_a1b2c3d4", "vid_e5f6g7h8"], "blend_scenes": true }, "webhookUrl": "https://example.com/video-webhook" }' ``` **Success response (200)** ```json { "request_id": "vid_join_a1b2c3", "session_id": "vid_join_a1b2c3" } ``` **Error responses** - `400` invalid/missing fields (unknown session id, incompatible sessions, etc.) - `401` invalid API key - `402` insufficient credits - `404` one or more sessions not found ## POST /video/remove_subtitles Clone an existing session, remove all subtitle/transcript text overlays (`type: "text"` items in layer `activeItemList`), and queue only frame + video generation for the new session. **Request body** ```json { "input": { "videoSessionId": "vid_a1b2c3d4" }, "webhookUrl": "https://example.com/webhook" } ``` - `videoSessionId` required; must be a video session that belongs to the API key. - Creates a new `session_id`; the source session remains unchanged. - The cloned session is configured to skip transcription and other generation stages, and run only frame/video generation. - This endpoint currently does not charge additional credits. **Sample request** ```bash curl -X POST https://api.samsar.one/v1/video/remove_subtitles \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "videoSessionId": "vid_a1b2c3d4" }, "webhookUrl": "https://example.com/video-webhook" }' ``` **Success response (200)** ```json { "request_id": "vid_nosub_a1b2c3", "session_id": "vid_nosub_a1b2c3" } ``` **Error responses** - `400` invalid/missing fields (invalid session id format, missing session id, etc.) - `401` invalid API key - `404` session not found ## POST /video/add_subtitles Clone an existing session, add subtitle/transcript text overlays, and queue the stages required to produce a subtitled video in a new session. **Request body** ```json { "input": { "videoSessionId": "vid_a1b2c3d4" }, "webhookUrl": "https://example.com/webhook" } ``` - `videoSessionId` required; must be a video session that belongs to the API key. - Creates a new `session_id`; the source session remains unchanged. - The cloned session is saved with `has_subtitles: true` and preserves the session language/aspect ratio. - This endpoint currently does not charge additional credits. **Sample request** ```bash curl -X POST https://api.samsar.one/v1/video/add_subtitles \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "videoSessionId": "vid_a1b2c3d4" }, "webhookUrl": "https://example.com/video-webhook" }' ``` **Success response (200)** ```json { "request_id": "vid_sub_a1b2c3", "session_id": "vid_sub_a1b2c3" } ``` **Error responses** - `400` invalid/missing fields (invalid session id format, missing session id, etc.) - `401` invalid API key - `404` session not found ## POST /video/cancel_render Cancel an in-progress render for an existing session. This sets the session to a cancelled state and stops queued render tasks for that session. **Request body** ```json { "input": { "videoSessionId": "vid_a1b2c3d4" } } ``` - `videoSessionId` required; must belong to the API key. - This endpoint does not create a new session. - This endpoint currently does not charge additional credits. **Sample request** ```bash curl -X POST https://api.samsar.one/v1/video/cancel_render \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "videoSessionId": "vid_a1b2c3d4" } }' ``` **Success response (200)** ```json { "request_id": "vid_a1b2c3d4", "session_id": "vid_a1b2c3d4", "status": "CANCELLED", "cancelled": true, "message": "Render cancellation requested." } ``` **Error responses** - `400` invalid/missing session id - `401` invalid API key - `404` session not found ## GET /video/fetch_latest_version Fetch the latest available render URL for a given video `session_id`. **Request** ```bash curl "https://api.samsar.one/v1/video/fetch_latest_version?session_id=vid_a1b2c3d4" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` **Completed (200)** ```json { "session_id": "vid_a1b2c3d4", "result_url": "https://cdn.samsar.one/videos/vid_a1b2c3d4.mp4", "has_subtitles": true, "result_language": "en" } ``` - `has_subtitles` is `true`, `false`, or `null` when subtitle state is unknown for older sessions. - `result_language` is the language code saved on the video session. **Pending (202)** ```json { "session_id": "vid_a1b2c3d4", "status": "PENDING", "message": "Render not ready yet." } ``` **Failed (200)** ```json { "session_id": "vid_a1b2c3d4", "status": "FAILED", "message": "Video generation failed." } ``` **Cancelled (200)** ```json { "session_id": "vid_a1b2c3d4", "status": "CANCELLED", "message": "Render was cancelled." } ``` **Error responses** - `400` missing/invalid `session_id` - `401` invalid API key - `403` session does not belong to the API key - `404` session not found ## GET /video/list_completed_video_sessions List completed video sessions for the authenticated API key, including each session language and final URL. **Request** ```bash curl "https://api.samsar.one/v1/video/list_completed_video_sessions" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` Optional query params: - `limit` (number): maximum number of sessions to return (server-capped to `5000`). **Success response (200)** ```json [ { "session_id": "vid_a1b2c3d4", "langauge": "en", "result_language": "en", "has_subtitles": true, "result_url": "https://static.samsar.one/video/vid_a1b2c3d4/final.mp4" }, { "session_id": "vid_e5f6g7h8", "langauge": "es", "result_language": "es", "has_subtitles": false, "result_url": "https://static.samsar.one/video/vid_e5f6g7h8/final.mp4" } ] ``` - Returns only sessions that have a final video URL available. - `langauge` is returned as a normalized language code (lowercase). - `result_language` is the same normalized language code returned with the corrected field name. - `has_subtitles` is `true`, `false`, or `null` when subtitle state is unknown for older sessions. **Error responses** - `400` missing API key/auth header - `401` invalid API key ## GET /status Poll the status for any `request_id` returned by the video endpoints (or `/image/*` endpoints). **Request** ```bash curl "https://api.samsar.one/v1/status?request_id=vid_a1b2c3d4" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` **Queued / processing** ```json { "session_id": "vid_a1b2c3d4", "request_id": "vid_a1b2c3d4", "status": "PENDING", "type": "video", "provider": "RUNWAYML", "expressGenerationStatus": { "prompt_generation": "PENDING", "video_generation": "IN_PROGRESS" } } ``` **Completed** ```json { "session_id": "vid_a1b2c3d4", "request_id": "vid_a1b2c3d4", "status": "COMPLETED", "type": "video", "provider": "RUNWAYML", "result_url": "https://cdn.samsar.one/videos/vid_a1b2c3d4.mp4", "result_urls": [ "https://cdn.samsar.one/videos/vid_a1b2c3d4.mp4" ], "has_subtitles": true, "result_language": "en", "videoLink": "https://cdn.samsar.one/videos/vid_a1b2c3d4.mp4", "remoteURL": "https://cdn.samsar.one/videos/vid_a1b2c3d4.mp4" } ``` - `result_urls` may be present when multiple assets are produced; `videoLink` or `remoteURL` can also contain the download URL. - Completed video responses include `has_subtitles` and `result_language` when session metadata is available. - `expressGenerationStatus` is present while individual stages are running; `expressGenerationError`/`message` is present on failure. - `404` is returned when the `request_id` is unknown; `401` when the API key is invalid. ## GET /supported_fonts Fetch the supported subtitle fonts by language code. Use this to validate `font_key` or text overlay choices before submitting video requests. This endpoint does not require authentication. **Request** ```bash curl "https://api.samsar.one/v1/supported_fonts" ``` **Success response (200)** ```json { "fontsByLanguage": { "default": ["Rampart One", "Montserrat", "Arial", "sans-serif"], "en": ["Poppins", "Montserrat", "Arial", "sans-serif"], "ja": ["Noto Sans JP", "M PLUS Rounded 1c", "Hiragino Sans", "Yu Gothic UI", "sans-serif"], "ko": ["Noto Sans KR", "Pretendard", "Apple SD Gothic Neo", "Malgun Gothic", "sans-serif"] } } ``` - `default` is used when a language code has no explicit mapping. - Font previews: visit [`/fonts`](/fonts) for light/dark samples with keys. ## V2 API The `/v2` omni routes now live in [V2 API](/v2), with left-nav sections for authentication, user credits, app keys, media generation, assistant utilities, status, and publishing routes. Reusable singular and branched narrative generation is documented in the [Narrative API](/narrative), including the route that starts media rendering from a completed `NarrativeRequest`. CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/assistant-api.mdx METADATA: title: Assistant API description: Call OpenAI-compatible assistant completions tied to Samsar sessions, system prompts, and multimodal outputs. image: img/og/assistant-api.png sidebar_position: 4 slug: /assistant-api sidebar_label: Assistant API PRIMARY_HEADING: Assistant API CONTENT_START # Assistant API Compare [inference models and adapters](/models?modality=inference) before choosing a workflow. The session assistant contract below is distinct from [provider-style completions](/external-requests#chat); available model selections depend on the endpoint. **Quick facts** - Base URL: `https://api.samsar.one/v1` - Route prefix: `/assistant · alias /api/v1/assistant` - Authentication: `Bearer API key` - Execution: `Synchronous · OpenAI-compatible Responses payload` - Billing: `Actual token usage at the 1.5× assistant rate` - Content type: `application/json` Use this API when you want OpenAI-style assistant behavior tied to an existing Samsar `session_id`. The assistant uses the customer account's configured assistant model and the account-level system prompt if one is set. If no custom system prompt exists, Samsar applies a minimal default assistant prompt. Reuse the same `session_id` across turns and Samsar will preserve the underlying OpenAI response chain, including image-generation turns. For provider-style OpenAI-compatible chat completions that can be persisted and polled across network resets, use the separate [`/v2/external/assistant/*` aliases](/external-requests#external-assistant-and-chat-completions). Those aliases do not use this page's session-bound Responses chain. ## POST /assistant/set_system_prompt Store or clear the account-level system prompt used for assistant completions. **Request body** ```json { "system_prompt": "You are the launch assistant for Acme. Keep answers concise, on-brand, and commercially practical." } ``` - `system_prompt` optional string. - Pass `null` or an empty string to clear the custom prompt and revert to Samsar's default prompt. **Sample request** ```bash curl -X POST https://api.samsar.one/v1/assistant/set_system_prompt \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "system_prompt": "You are the launch assistant for Acme. Keep answers concise, on-brand, and commercially practical." }' ``` **Success response (200)** ```json { "system_prompt": "You are the launch assistant for Acme. Keep answers concise, on-brand, and commercially practical.", "model": "gpt-6-astra", "selected_assistant_model": "gpt-6-astra" } ``` **Common errors** - `400` invalid payload. - `401` invalid API key. --- ## POST /assistant/completion Create a synchronous assistant response for an existing Samsar session. The request returns an OpenAI-compatible Responses payload so it can fit cleanly into modern OpenAI client flows. You must provide a `session_id`. You can optionally send fresh user input in the same request. If you omit `input`, the assistant responds from the existing session history. **Request body** ```json { "session_id": "67df0d8ebc4f9d0b7f4fd123", "input": [ { "role": "user", "content": [ { "type": "input_text", "text": "Write a short product launch caption for this session." } ] } ], "max_output_tokens": 300 } ``` - `session_id` required. - `input` optional: - a string - a single message object - an array of message objects - a multimodal content array such as `input_text` and `input_image` - `max_output_tokens`, `temperature`, `top_p`, `reasoning`, `metadata`, `tools`, and `tool_choice` are accepted when supported by the configured assistant model. - To enable assistant-generated images, pass `tools: [{ "type": "image_generation" }]`. If you want to force an image response for the turn, set `tool_choice` to `{ "type": "image_generation" }`. **Multimodal example** ```json { "session_id": "67df0d8ebc4f9d0b7f4fd123", "input": [ { "role": "user", "content": [ { "type": "input_text", "text": "Describe the hero image and turn it into a headline." }, { "type": "input_image", "image_url": "https://cdn.example.com/launch-hero.png" } ] } ] } ``` **Image generation example** ```json { "session_id": "67df0d8ebc4f9d0b7f4fd123", "input": "Create a clean launch-poster image for this session.", "tools": [ { "type": "image_generation", "size": "1024x1024", "quality": "high" } ], "tool_choice": { "type": "image_generation" } } ``` **Sample request** ```bash curl -X POST https://api.samsar.one/v1/assistant/completion \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "session_id": "67df0d8ebc4f9d0b7f4fd123", "input": [ { "role": "user", "content": [ { "type": "input_text", "text": "Write a short product launch caption for this session." } ] } ], "max_output_tokens": 300 }' ``` **Success response (200)** ```json { "id": "resp_123", "object": "response", "created_at": 1773651000, "status": "completed", "model": "gpt-6-astra", "output_text": "Meet the new drop: sharp design, fast story, and a launch-ready look in one line.", "output": [ { "id": "resp_123_message", "type": "message", "role": "assistant", "content": [ { "type": "output_text", "text": "Meet the new drop: sharp design, fast story, and a launch-ready look in one line.", "annotations": [] } ] } ], "usage": { "input_tokens": 482, "output_tokens": 43, "total_tokens": 525 } } ``` **Image response example** ```json { "id": "resp_456", "object": "response", "created_at": 1773651300, "status": "completed", "model": "gpt-6-astra", "output": [ { "id": "ig_456", "type": "image_generation_call", "status": "completed", "revised_prompt": "A clean launch poster with a premium layout, strong product focal point, and minimal copy.", "result": "iVBORw0KGgoAAAANSUhEUgAA..." } ], "usage": { "input_tokens": 521, "output_tokens": 22, "total_tokens": 543 } } ``` The `result` field contains a base64-encoded image. For follow-up image edits, keep using the same `session_id`; Samsar preserves the latest response chain automatically. **Headers** - `x-credits-charged` - `x-credits-remaining` **Common errors** - `400` missing or invalid `session_id`, or invalid `input`. - `401` invalid API key. - `402` insufficient credits. - `403` session does not belong to the authenticated account. - `404` session not found. CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/chat-api.mdx METADATA: title: Chat API description: Improve copy, run managed chat and moderation, and build embedding workflows through SamsarOne. image: img/og/chat-api.png sidebar_position: 3 slug: /chat-api sidebar_label: Chat API PRIMARY_HEADING: Chat API CONTENT_START # Chat API Explore the [inference and vision matrix](/models?modality=inference) and [embedding services](/models?modality=embeddings). Hosted **samsar-js** is the common client; native inference and local search requirements are covered in the [adapter guide](/adapters). Send a sample prompt with your API key in the Playground to see how the `/chat/enhance` response is formatted. **Quick facts** - Base URL: `https://api.samsar.one/v1` - Route prefix: `Managed inference: /v2/external` - Authentication: `Bearer API key` - Execution: `Core chat is synchronous; managed inference also supports polling` - Billing: `Flat, token-based, or free by route` `/chat/enhance` costs **30 credits/request**. Managed chat uses token-based adapter rates, moderation is free, and embedding costs scale with processed text. See the managed inference rate tables for a compact comparison. ## POST /chat/enhance Improve a message while keeping its intent. `metadata` is optional contextual data that influences the rewrite. Provide `language` (ISO 639-1 code or language name) if you want the output in a specific language; otherwise detection is automatic. **Request body** ```json { "message": "Draft caption text", "metadata": { "audience": "gen z", "tone": "playful" }, "language": "FR" } ``` - `message` required, non-empty string. - `metadata` optional object. - `language` optional string, ISO 639-1 code (e.g., `EN`, `FR`, `TH`) or language name; defaults to auto-detect. **Sample request** ```bash curl -X POST https://api.samsar.one/v1/chat/enhance \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": "launching soon, stay tuned!", "metadata": { "channel": "instagram" }, "language": "FR" }' ``` **Success response (200)** ```json { "content": "We’re launching soon—follow along for first looks and drop reminders!" } ``` **Common errors** - `400` missing or empty `message`. - `401` invalid API key. - `402` insufficient credits. --- ## POST /v2/external/chat/completions Create an OpenAI-compatible chat completion billed through Samsar credits. Use this endpoint when an external deployment or integration needs managed inference without supplying native OpenAI or Google credentials. The same handler is available at `/v2/external/chat`, `/v2/external/assistant`, and `/v2/external/assistant/completions`; `/api/v2/external/*` is also supported. This endpoint is synchronous by default. For durable submission, idempotent retries, request correlation, and polling, see [External assistant and chat completions](/external-requests#external-assistant-and-chat-completions). **Pricing** Managed inference applies a **1.25×** multiplier to actual token usage. Current request keys are `gpt-6-astra`, `gemini-3.1-pro`, `QWEN3.8`, and `kimi-k3`; `QWEN3.8` uses Qwen 3.8 Max for both text and vision, and billing follows the provider adapter that handled the request. Use the managed inference table for exact input, cached-input, long-context, and output rates. Vision uses the same adapter rates for image-derived input and generated output tokens. Responses include `x-credits-charged` and `x-credits-remaining` when credits are deducted. **Request body** ```json { "model": "gpt-6-astra", "messages": [ { "role": "developer", "content": "Return concise JSON." }, { "role": "user", "content": "Create a three-scene outline for a launch video." } ], "response_format": { "type": "json_object" } } ``` For vision, send OpenAI-compatible image message parts: ```json { "model": "gemini-3.1-pro", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image for a video prompt." }, { "type": "image_url", "image_url": { "url": "https://example.com/frame.png" } } ] } ] } ``` **Sample request** ```bash curl -X POST https://api.samsar.one/v2/external/chat/completions \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-6-astra", "messages": [ { "role": "user", "content": "Write one cinematic product video theme." } ] }' ``` **Success response (200)** The response is an OpenAI-compatible chat completion object. Queued mode instead returns HTTP `202` with `request_id`, `status`, and `poll_url`. The default server execution timeout is 10 minutes; polling avoids holding the HTTP connection for that entire period. **Common errors** - `400` missing or invalid `messages`. - `401` invalid API key. - `402` insufficient credits. --- ## POST /v2/external/moderation Create an OpenAI-compatible moderation check for external deployments. The same handler is available at `/external/moderations`, `/v2/external/moderation`, and `/v2/external/moderations`. This route requires a Samsar API key but does not charge credits. **Request body** ```json { "input": "A user prompt or generated draft to moderate.", "model": "omni-moderation-latest" } ``` **Sample request** ```bash curl -X POST https://api.samsar.one/v2/external/moderation \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": "Create a family-safe product launch video." }' ``` **Success response (200)** The response includes the OpenAI-compatible moderation result plus a `decision` object with the normalized Samsar pass/fail decision. **Common errors** - `400` missing or invalid `input`. - `401` invalid API key. - `500` moderation provider is not configured. --- ## POST /chat/create_embedding Create a new embedding template from either a JSON array or a URL list. JSON records are embedded directly. URL inputs are first crawled with Firecrawl, minimally cleaned, normalized into the same compatible record structure, and then embedded using `text-embedding-3-large`. **Request body** ```json { "name": "listings", "records": [ { "id": "A1", "city": "Austin", "price": 1800, "duration": 12, "description": "Modern 1BR close to downtown." } ] } ``` - `records` required when embedding JSON input. - `urls` or `url` can be used instead of `records` to build embeddings from crawled web pages. - `name` optional string. Also accepted: `embedding_name`, `template_name`. - Each record should include an `id`, `_id`, or equivalent identifier. If missing, the API generates one per record. - `field_options` optional map (or array) of per-field flags: - `searchable: false` excludes the field (and nested paths) from embedding input. - `filterable: true` includes the field in structured filters used for search/similar queries. - `filterable: false` excludes the field from structured filters. - `retrievable: false` removes the field from raw `record` payloads in search results. - Use dot notation (e.g. `owner.email`) for nested fields. You can also inline flags in records using a `{ value, searchable, filterable, retrievable }` wrapper. ```json { "records": [ { "id": "A1", "internal_notes": { "value": "private note", "searchable": false, "retrievable": false } } ] } ``` **URL request body** ```json { "name": "product-docs", "urls": [ "https://example.com/docs/getting-started", "https://example.com/docs/pricing" ] } ``` - URL mode crawls each URL with Firecrawl before embedding. - Optional `levels` controls crawl depth for URL mode. It can be passed as a query param or body field. - `1`: crawl only the listed seed URLs. - `2`: crawl each seed URL plus URLs linked directly from it. - `3`: crawl each seed URL plus two link hops outward. - Default: `2`. Maximum: `3`. - URL mode is capped at `50` total crawled pages per request across all seed URLs. - Returned embeddings remain compatible with `/chat/search_against_embedding` and `/chat/similar_to_embedding`. - URL mode responses may include `input_url_count`, `processed_url_count`, `firecrawl_credits_used`, `skipped_urls`, and `crawl_errors`. **Sample request** ```bash curl -X POST "https://api.samsar.one/v1/chat/create_embedding?levels=2" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "listings", "field_options": { "description": { "searchable": true }, "internal_notes": { "searchable": false, "retrievable": false }, "price": { "filterable": true } }, "records": [ { "id": "A1", "city": "Austin", "price": 1800, "description": "Modern 1BR close to downtown." }, { "id": "A2", "city": "Dallas", "price": 2200, "description": "Spacious 2BR with a balcony." } ] }' ``` **Success response (200)** ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "template_hash": "b0e6f2d2c2f1d0c4d9f7f3a1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9", "hash_link": "embedding_template:b0e6f2d2c2f1d0c4d9f7f3a1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9", "record_count": 2, "structured_fields": [ { "key": "city", "type": "string" }, { "key": "price", "type": "number" } ], "unstructured_fields": ["description"] } ``` **Common errors** - `400` missing `records` and `urls`, or invalid URL input. - `401` invalid API key. - `402` insufficient credits. --- ## POST /chat/create_embedding_from_url Create a new embedding template from one URL or a URL list. This is the explicit URL-only variant of `/chat/create_embedding` and uses the same Firecrawl-backed ingestion flow. **Request body** ```json { "name": "product-docs", "urls": [ "https://example.com/docs/getting-started", "https://example.com/docs/pricing" ] } ``` - `urls` required array of URL strings. `url` is also accepted for a single URL. - Optional `levels` controls crawl depth for URL mode. It can be sent as a query param or body field. - `1`: crawl only the listed seed URLs. - `2`: crawl each seed URL plus URLs linked directly from it. - `3`: crawl each seed URL plus two link hops outward. - Default: `2`. Maximum: `3`. - URL mode is capped at `50` total crawled pages per request across all seed URLs. - `field_options` is optional and follows the same rules as `/chat/create_embedding`. - Samsar crawls the URLs, minimally cleans the extracted text, stores normalized URL metadata, and creates embeddings in the same query-compatible format as JSON-record embeddings. - Billing applies Firecrawl crawl usage at a **2.5x** multiplier based on actual pages crawled, then applies the embedding token cost with its own **2.5x** multiplier. **Sample request** ```bash curl -X POST "https://api.samsar.one/v1/chat/create_embedding_from_url?levels=2" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "product-docs", "urls": [ "https://example.com/docs/getting-started", "https://example.com/docs/pricing" ] }' ``` **Success response (200)** ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "template_hash": "b0e6f2d2c2f1d0c4d9f7f3a1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9", "hash_link": "embedding_template:b0e6f2d2c2f1d0c4d9f7f3a1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9", "record_count": 2, "input_url_count": 2, "processed_url_count": 12, "firecrawl_credits_used": 12, "crawl_levels": 2, "max_links": 50, "structured_fields": [ { "key": "hostname", "type": "string" }, { "key": "status_code", "type": "number" } ], "unstructured_fields": ["title", "description", "content"] } ``` **Common errors** - `400` missing or invalid `urls`. - `401` invalid API key. - `402` insufficient credits. - `422` URLs crawled successfully but produced no extractable content. - `5xx` Firecrawl crawl failure or upstream processing failure. --- ## POST /chat/generate_embeddings_from_plain_text Create a new embedding template from already cleaned plain text input. This route is the embedding-only stage of the URL ingestion flow: you provide the cleaned text directly, Samsar skips crawling, and it stores the result in the same query-compatible embedding format used by the JSON and URL endpoints. **Request body** ```json { "name": "product-docs-clean", "plain_text": [ { "url": "https://example.com/docs/getting-started", "title": "Getting Started", "content": "Cleaned plain text content from the page." } ] } ``` - `plain_text` required. Accepted shapes: - a single string - a single object - an array of strings or objects - For object entries, the cleaned text can be provided in `plain_text`, `content`, `text`, `cleaned_text`, `markdown`, or `body`. - Optional object fields such as `url`, `title`, `description`, and `language` are preserved and normalized into the stored record when present. - `field_options` is optional and follows the same rules as `/chat/create_embedding`. - Pricing uses the embedding token cost with a **2.5x** multiplier and does **not** charge any crawl credits. **Sample request** ```bash curl -X POST https://api.samsar.one/v1/chat/generate_embeddings_from_plain_text \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "product-docs-clean", "plain_text": [ { "url": "https://example.com/docs/getting-started", "title": "Getting Started", "content": "Cleaned plain text content from the page." }, { "url": "https://example.com/docs/pricing", "title": "Pricing", "content": "Cleaned pricing page text." } ] }' ``` **Success response (200)** ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "template_hash": "b0e6f2d2c2f1d0c4d9f7f3a1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9", "hash_link": "embedding_template:b0e6f2d2c2f1d0c4d9f7f3a1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9", "record_count": 2, "structured_fields": [ { "key": "hostname", "type": "string" } ], "unstructured_fields": ["title", "content"] } ``` **Common errors** - `400` missing or invalid `plain_text`. - `401` invalid API key. - `402` insufficient credits. --- ## POST /chat/update_embedding Append or update records for an existing template. New records are embedded and merged into the template schema; matching `sourceId` records are upserted. **Request body** ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "field_options": { "description": { "searchable": true }, "internal_notes": { "searchable": false, "retrievable": false }, "price": { "filterable": true } }, "records": [ { "id": "A3", "city": "Houston", "price": 2000, "description": "Bright 2BR near the park." } ] } ``` `field_options` follows the same rules as `/chat/create_embedding`. If omitted, the template's existing field options are reused. **Success response (200)** ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "template_hash": "9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1908", "record_count": 3, "structured_fields": [ { "key": "city", "type": "string" }, { "key": "price", "type": "number" } ], "unstructured_fields": ["description"] } ``` **Common errors** - `400` missing `template_id` or `records`. - `401` invalid API key. - `402` insufficient credits. - `404` template not found. --- ## POST /chat/delete_embeddings Delete all embeddings for a template. The template remains, but its record count becomes 0. **Request body** ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3" } ``` **Success response (200)** ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "deleted_count": 250, "record_count": 0, "status": "empty" } ``` **Common errors** - `400` missing `template_id`. - `401` invalid API key. - `404` template not found. --- ## POST /chat/delete_embedding Delete one or more embeddings from a template by record id. Use the `id` returned in search results or the `id` you supplied in the original records. **Request body** ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "source_id": "A2" } ``` - `source_id` optional string. Also accepted: `sourceId`, `id`, `record_id`. - `source_ids` optional array of strings. Also accepted: `sourceIds`, `ids`, `record_ids`. - Provide either `source_id` or `source_ids`. **Success response (200)** ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "deleted_count": 1, "record_count": 249, "status": "ready" } ``` **Common errors** - `400` missing `template_id` or `source_id`/`source_ids`. - `401` invalid API key. - `404` template not found. --- ## POST /chat/search_against_embedding Search within a template using a natural language query. The API extracts structured filters first, then performs vector similarity. Optional rerank can re-order top results. **Request body** ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "search_term": "2 bedroom apartment in Dallas under 2500", "search_params": { "city": "Dallas", "price": { "max": 2500 } }, "limit": 10, "num_candidates": 50, "rerank": true, "include_raw": true } ``` **Success response (200)** ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "template_name": "listings", "structured_filters": { "city": "Dallas", "price": 2500 }, "results": [ { "id": "A2", "score": 0.92, "structured_filters": { "city": "Dallas", "price": 2200 }, "record": { "id": "A2", "city": "Dallas", "price": 2200, "description": "Spacious 2BR with a balcony." } } ] } ``` - Optional `structured_filters` (or `filters`) can be supplied to force filters. - Optional `search_params` (or `searchParams`) supplies a filter payload validated against the template schema and applied before vector search. `structured_filters` overrides overlapping keys. - Use `include_raw: false` to omit the original record payloads. **Common errors** - `400` missing `template_id` or `search_term`. - `401` invalid API key. - `402` insufficient credits. - `404` template not found. --- ## POST /chat/similar_to_embedding Return similar results for a query within a template (vector similarity only). You can pass a natural language `search_term` or a JSON payload in `search_json` which is converted into the template's search document before embedding. **Request body** ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "search_term": "modern downtown apartment", "search_params": { "city": "Austin" }, "limit": 10, "min_results": 10 } ``` **Request body (JSON search)** ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "search_json": { "city": "Austin", "price": 2000, "description": "modern downtown apartment" }, "limit": 10 } ``` - Provide `search_term` or `search_json`. - Optional `search_params` (or `searchParams`) prefilters by structured fields (validated against the template schema) before vector similarity. - Optional `min_results` ensures at least that many results are requested (may still return fewer if the template has fewer matches). **Success response (200)** ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "structured_filters": {}, "matches": [ { "id": "A1", "score": 0.88 }, { "id": "A2", "score": 0.73 } ] } ``` **Common errors** - `400` missing `template_id` or `search_term`/`search_json`. - `401` invalid API key. - `402` insufficient credits. - `404` template not found. --- ## GET /chat/embedding_templates List embedding templates available for the API key. **Query params** - `limit` optional integer (default 50, max 100). - `offset` optional integer (default 0). **Sample request** ```bash curl -X GET "https://api.samsar.one/v1/chat/embedding_templates?limit=25&offset=0" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` **Success response (200)** ```json { "templates": [ { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "name": "listings", "template_hash": "b0e6f2d2c2f1d0c4d9f7f3a1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9", "hash_link": "embedding_template:b0e6f2d2c2f1d0c4d9f7f3a1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9", "record_count": 3, "structured_fields": [ { "key": "city", "type": "string" }, { "key": "price", "type": "number" } ], "unstructured_fields": ["description"], "embedding_model": "text-embedding-3-large", "created_at": "2024-11-03T12:00:00.000Z", "updated_at": "2024-11-03T12:10:00.000Z" } ], "pagination": { "total": 1, "limit": 25, "offset": 0, "has_more": false } } ``` **Common errors** - `401` invalid API key. --- ## GET /chat/embedding_status Check whether embeddings exist for a template id. **Query params** - `template_id` required string. **Sample request** ```bash curl -X GET "https://api.samsar.one/v1/chat/embedding_status?template_id=66f3b0d64c8bca9c6f2bd1a3" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` **Success response (200)** ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "has_embeddings": true, "record_count": 3, "status": "ready" } ``` **Common errors** - `400` missing `template_id`. - `401` invalid API key. - `404` template not found. CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/credits-api.mdx METADATA: title: Credits API description: Check credit balances, create recharge links, and manage auto-recharge controls through the Samsar credits API. image: img/og/credits-api.png sidebar_position: 4 slug: /credits sidebar_label: Credits API PRIMARY_HEADING: Credits API CONTENT_START # Credits API **Quick facts** - Base URL: `https://api.samsar.one/v1` - Route prefix: `/credits` - Authentication: `Bearer API key` - Execution: `Synchronous account and checkout controls` - Billing: `100 credits = $1 USD` - Content type: `application/json` Credits are billed at **1 USD = 100 credits**. Use these endpoints to check remaining credits and to generate a Stripe checkout link that tops up credits through the webhook. ## GET /credits Fetch the current credit balance and the most recent top-up (if available). **Sample request** ```bash curl https://api.samsar.one/v1/credits \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` **Success response (200)** ```json { "remainingCredits": 4920, "lastTopUp": { "id": "66e21f9f1f3b0f0001c7a1a2", "amountPaidCents": 5000, "currency": "USD", "paymentType": "one_time", "paymentStatus": "paid", "billingReason": "one_time", "creditsApplied": 5000, "paymentDate": "2024-09-12T10:08:11.000Z", "stripeInvoiceId": "in_1Pxxx", "stripeInvoiceNumber": "B7A2-1", "invoicePdfUrl": "https://...", "hostedInvoiceUrl": "https://...", "receiptUrl": "https://...", "receiptAvailable": true, "productSummary": "Purchase 5000 credits" } } ``` **Response headers** - `x-credits-remaining` returns the current balance. ## POST /credits/recharge Generate a Stripe checkout link to recharge a specific number of credits. **Request body** ```json { "credits": 2500 } ``` - `credits` is required, positive, and must be an integer. - Price is calculated as `credits / 100` in USD. - Use `/payment_status` with `checkoutSessionId` or `paymentIntentId` to poll for completion. **Sample request** ```bash curl -X POST https://api.samsar.one/v1/credits/recharge \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"credits": 2500}' ``` **Success response (200)** ```json { "url": "https://checkout.stripe.com/c/pay/cs_test_...", "checkoutSessionId": "cs_test_123", "paymentIntentId": "pi_123", "paymentStatusEndpoint": "/v1/payment_status", "credits": 2500, "amountUsd": 25, "amountCents": 2500, "currency": "USD" } ``` **Common errors** - `400` invalid or missing `credits`. - `401` invalid API key. ## POST /enable_autorecharge Enable or update auto-recharge settings for the API key. If no saved payment method exists, the response includes a Stripe setup URL to complete the enablement flow. **Request body** ```json { "thresholdCredits": 1000, "amountUsd": 50, "maxMonthlyUsd": 200, "requestSetupSession": true } ``` - `thresholdCredits` (optional): auto-recharge triggers when credits fall below this value. - `amountUsd` (optional): charge amount in USD for each auto-recharge (1 USD = 100 credits). - `maxMonthlyUsd` (optional): cap auto-recharge spend per calendar month. Set to `0` to disable the cap. - `requestSetupSession` (optional): force a Stripe setup session if no payment method exists. - You can also send the payload under `input` (e.g. `{ "input": { ... } }`). **Sample request** ```bash curl -X POST https://api.samsar.one/v1/enable_autorecharge \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "thresholdCredits": 1000, "amountUsd": 50, "maxMonthlyUsd": 200, "requestSetupSession": true }' ``` **Success response (200)** ```json { "autoRechargeEnabled": false, "autoRechargeAmountUsd": 50, "autoRechargeThreshold": 1000, "autoRechargeMaxMonthlyUsd": 200, "stripeCustomerId": "cus_123", "setupSessionUrl": "https://checkout.stripe.com/c/setup/...", "setupSessionId": "cs_test_456", "setupIntentId": "seti_123", "paymentStatusEndpoint": "/v1/payment_status", "url": "https://checkout.stripe.com/c/setup/...", "hasPaymentMethod": false, "autoRechargeRun": null } ``` **Notes** - After Stripe setup completes, auto-recharge is enabled via the webhook and will trigger when credits drop below the threshold. - If `maxMonthlyUsd` is set, auto-recharges are capped to the remaining monthly allowance; charges are skipped once the cap is reached. - Use `/payment_status` with `setupSessionId` or `setupIntentId` to poll setup completion. ## GET /payment_status Poll for the status of a Stripe checkout, payment intent, or setup intent created by the credits endpoints. **Query params** - `checkoutSessionId` (or `checkout_session_id`): Stripe Checkout Session id. - `paymentIntentId` (or `payment_intent_id`): Stripe PaymentIntent id. - `setupIntentId` (or `setup_intent_id`): Stripe SetupIntent id (auto-recharge setup). **Sample request** ```bash curl "https://api.samsar.one/v1/payment_status?checkoutSessionId=cs_test_123" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` **Success response (200)** ```json { "status": "pending", "mode": "payment", "checkoutSessionId": "cs_test_123", "sessionStatus": "open", "paymentStatus": "unpaid", "paymentIntentId": "pi_123", "paymentIntentStatus": "requires_payment_method", "amountCents": 2500, "currency": "usd" } ``` **Status values** - `pending`: still in progress. - `succeeded`: payment/setup completed. - `failed`: payment/setup failed or session expired. **Common errors** - `400` missing status identifier. - `403` session/intent does not belong to the requesting API key. ## POST /auto_recharge/threshold Update the auto-recharge threshold for an account that already has auto-recharge enabled. **Request body** ```json { "thresholdCredits": 1500 } ``` - `thresholdCredits` is required and must be a non-negative number. - Auto-recharge must already be enabled, otherwise the endpoint returns a 400 error. **Sample request** ```bash curl -X POST https://api.samsar.one/v1/auto_recharge/threshold \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"thresholdCredits": 1500}' ``` **Success response (200)** ```json { "autoRechargeEnabled": true, "autoRechargeThreshold": 1500, "autoRechargeAmountUsd": 50, "autoRechargeMaxMonthlyUsd": 200 } ``` **Common errors** - `400` auto-recharge is not enabled (enable via `/enable_autorecharge` or the billing page). CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/external-requests-api.mdx METADATA: title: External Requests API description: Use hosted Samsar.js for managed chat, image, video and audio, then explore supported deployment adapters. image: img/og/external-requests-api.png sidebar_position: 6 slug: /external-requests sidebar_label: External Requests API PRIMARY_HEADING: External Requests API CONTENT_START # External Requests API **Quick facts** - Base URL: `https://api.samsar.one` - Route prefix: `/v2/external` - Authentication: `Bearer Samsar API key` - Execution: `Provider-style calls; sync or async by route` - Billing: `Endpoint-specific; credit headers report usage` - Content type: `application/json` Legacy `/external/*` aliases may exist on some deployments, but new integrations should use `/v2/external/*`. Use this surface through **samsar-js** to call hosted Samsar as a managed provider for chat, images, video and audio. A standalone installation can choose the Samsar adapter alongside supported native providers; its validated configuration and saved model preferences determine runtime order. See the [model matrix](/models) and [deployment adapter guide](/adapters). This is different from `/external_users/*`. External users track per-customer credits, history, and login state. External requests are one-off provider-style calls authenticated by a Samsar API key. ## Validate a Samsar API key `GET /v2/external/api_key/validate` ```bash curl "https://api.samsar.one/v2/external/api_key/validate" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` Success response: ```json { "valid": true, "authType": "api_key", "email": "admin@example.com", "remainingCredits": 12000 } ``` ## Provider capabilities `GET /v2/external/providers/capabilities` Returns the provider, model, and action capability map used by deployment onboarding. ```bash curl "https://api.samsar.one/v2/external/providers/capabilities" ``` ## Validate deployment providers `POST /v2/external/providers/validate` Use this from the local deployment wizard to validate optional provider credentials before enabling model settings. The SDK sends credentials only to the local processor or configured Samsar API host; do not persist provider secrets in client code. ```bash curl -X POST "https://api.samsar.one/v2/external/providers/validate" \ -H "Content-Type: application/json" \ -d '{ "samsarApiKey": "sk_live_...", "openaiApiKey": "sk-...", "falApiKey": "fal_...", "runwayApiKey": "rw_..." }' ``` Success response: ```json { "providers": { "samsar": { "ok": true, "status": "valid", "remainingCredits": 12000 } }, "available": { "providers": ["samsar"], "models": ["GOOGLE_TTS", "GPTIMAGE2.5", "HAPPYHORSEI2V", "KIMIK3", "LATENT_SYNC", "LYRIA3", "MMAUDIO", "OPENAI_TTS", "QWEN3.8", "RUNWAYML", "VEO3.1I2V", "WAN2.7PRO", "gemini-3.1-pro", "gpt-6-astra"], "actions": ["assistant", "audio", "chat", "image", "lip_sync", "moderation", "sound_effect", "video"] } } ``` ## Chat ### External assistant and chat completions The external assistant endpoints accept OpenAI-compatible chat completion payloads. They are intended for deployed Samsar environments and other integrations that use Samsar as a managed inference provider. The `/assistant/*` paths below are naming aliases for the same provider-style chat handler. They are separate from the session-bound [`POST /v1/assistant/completion`](/assistant-api#post-assistantcompletion) API. ### Routes Create a completion with any of these `POST` routes: - `/v2/external/chat` - `/v2/external/chat/completions` — preferred for OpenAI-compatible integrations - `/v2/external/assistant` - `/v2/external/assistant/completions` Read queued request status with any of these `GET` routes: - `/v2/external/chat/status` - `/v2/external/chat/completions/status` - `/v2/external/assistant/status` The same routes are available under `/api/v2/external`. Legacy deployments may also expose the unversioned `/external` prefix. There is no `/v2/external/assistant/completions/status` route; use `/v2/external/assistant/status` or the canonical `/v2/external/chat/status` path. ### Synchronous completion Polling is opt-in. Without a polling control, the endpoint waits for inference and returns the raw OpenAI-compatible completion with HTTP `200`. ```bash curl -X POST "https://api.samsar.one/v2/external/chat/completions" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-6-astra", "messages": [ { "role": "user", "content": "Write a launch caption for a travel reel." } ], "max_tokens": 300 }' ``` The default server execution timeout is 10 minutes. `stream: true` is not supported. When available, billing is returned through these headers: - `x-credits-charged` - `x-credits-remaining` ### Queued completion with polling Use polling for long-running assistant work such as narrative or theme generation. It avoids holding one HTTP connection open for the entire model call and allows the request to recover after a client disconnect, network reset, or processor restart. Generate and persist a stable `client_request_id` before submitting. Retry the same logical submission with the same ID if the initial response is lost. ```bash curl -X POST "https://api.samsar.one/v2/external/assistant/completions" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-6-astra", "messages": [ { "role": "user", "content": "Create a cinematic theme for this video." } ], "response_mode": "polling", "client_request_id": "video-67df0d8e:theme:v1", "client_session_id": "video-67df0d8e", "client_request_key": "theme" }' ``` `async: true`, `poll: true`, or `response_mode: "async" | "poll" | "polling"` all enable queued mode. Prefer `response_mode: "polling"` for new integrations. The canonical fields are: | Field | Purpose | | --- | --- | | `client_request_id` | Optional idempotency key. Make it unique for one logical request and reuse it only when retrying that request. | | `client_session_id` | Optional local session correlation value. It is persisted and echoed only when `client_request_id` is present. | | `client_request_key` | Optional local stage or operation name, such as `theme` or `narrative`. It is persisted and echoed only when `client_request_id` is present. | | `timeout` / `timeoutMs` | Optional server execution timeout in milliseconds. The default is `600000` (10 minutes). | A queued request returns HTTP `202`: ```json { "request_id": "67df0d8ebc4f9d0b7f4fd123", "requestId": "67df0d8ebc4f9d0b7f4fd123", "status": "PENDING", "poll_url": "/v2/external/chat/status?request_id=67df0d8ebc4f9d0b7f4fd123", "client_request_id": "video-67df0d8e:theme:v1", "client_session_id": "video-67df0d8e", "client_request_key": "theme", "created_at": "2026-07-16T13:00:00.000Z", "updated_at": "2026-07-16T13:00:00.000Z" } ``` `poll_url` is a relative canonical path. Store `request_id` as soon as it is returned. If the submit response is lost, you can still look up the request by the persisted `client_request_id`. Poll by provider request ID: ```bash curl "https://api.samsar.one/v2/external/chat/status?request_id=67df0d8ebc4f9d0b7f4fd123" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` Or recover by client request ID: ```bash curl "https://api.samsar.one/v2/external/chat/status?client_request_id=video-67df0d8e%3Atheme%3Av1" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` Status reads are immediate rather than long-polling. Poll every 1–2 seconds until the request reaches a terminal state: | Status | Meaning | | --- | --- | | `PENDING` | Persisted and waiting to be claimed. Polling also repairs a request persisted immediately before a processor restart. | | `PROCESSING` | Claimed by the hosted processor. An expired worker lease can be reclaimed safely. | | `COMPLETED` | Terminal success. Read the OpenAI-compatible completion from `response`. | | `FAILED` | Terminal failure. Read the provider-safe details from `error`. | Completed status response: ```json { "request_id": "67df0d8ebc4f9d0b7f4fd123", "status": "COMPLETED", "response": { "id": "chatcmpl_123", "object": "chat.completion", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "A precise cinematic theme..." }, "finish_reason": "stop" } ] }, "creditsCharged": 12, "remainingCredits": 9988 } ``` Failed status response (HTTP `200`): ```json { "request_id": "67df0d8ebc4f9d0b7f4fd123", "status": "FAILED", "error": { "message": "External assistant request failed.", "code": null, "status": 500 } } ``` Request status is retained for 24 hours from creation. Idempotent submission replays return the original request and do not replace its payload; use a new `client_request_id` for an intentional retry after `FAILED` or `COMPLETED`. Fetch status to obtain completion credit headers and body fields, including after an idempotent replay. Common errors: - `400` missing authentication headers, missing or invalid `messages`, unsupported `stream: true`, or an invalid status lookup ID. - `401` invalid or expired credentials. - `402` insufficient credits for a synchronous request. - `403` the supplied credential type is not supported by this provider route. - `404` request not found for the authenticated account. - Async provider failures are represented by HTTP `200` with `status: "FAILED"` and the nested `error` object. ## Embeddings `POST /v2/external/embeddings` Create OpenAI-compatible embedding vectors with `text-embedding-3-small` at 1536 dimensions. Send one string or up to 100 strings; each input may contain up to 12,000 characters. Billing is **100 credits per 1M tokens**. ```bash curl -X POST "https://api.samsar.one/v2/external/embeddings" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": ["A launch-ready product description", "A concise campaign tagline"], "model": "text-embedding-3-small", "dimensions": 1536 }' ``` ## Image `/v2/external/image` mounts the canonical Image API behind external-provider authentication. The request bodies, status behavior, and pricing are unchanged; add `/v2/external` before the documented `/image/...` path. | Route | Purpose and billing | | --- | --- | | `POST /v2/external/image/text_to_image` | Generate images for **15 credits/output**. `/generate` and `/generations` are compatibility aliases. | | `POST /v2/external/image/assign_title` | Generate an image title with usage-based inference billing at **1.5×**. | | `POST /v2/external/image/enhance` | Enhance to `0.5k`, `1k`, `2k`, or `4k` for **11**, **15**, **22**, or **29 credits**. | | `POST /v2/external/image/remove_branding` | Remove a logo, watermark, or brand text for **15 credits/request**. | | `POST /v2/external/image/add_image_set` | Extend an image set for **15 credits/generated image**. | | `GET /v2/external/image/status` | Poll an asynchronous hosted image request. | The mounted surface also includes image history, receipt-template, receipt-query, and rollup-banner routes. Use the [Image API](/image-api) as the canonical contract and rate reference for those paths. Example: ```bash curl -X POST "https://api.samsar.one/v2/external/image/enhance" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "image_url": "https://cdn.example.com/photo.png", "resolution": "1k" }' ``` Async image routes return a `request_id`. Poll: ```bash curl "https://api.samsar.one/v2/external/image/status?request_id=img_enh_123" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` ## Video `POST /v2/external/video/{video_route}` The external video surface combines the canonical Video API with standalone provider-first routes for remote deployments that bill through a Samsar API key. Add `/v2/external` before a documented `/video/...` path to use the mounted canonical surface; the table below highlights the provider-first additions. | Route | Purpose | | --- | --- | | `POST /v2/external/video/text_to_video` | Create an express text-to-video request. | | `POST /v2/external/video/image_to_video` | Animate one public image directly; alias of the direct image-to-video handler. | | `POST /v2/external/video/direct_image_to_video` | Animate one public image directly, without image generation or the full express narrative pipeline. | | `POST /v2/external/video/narrative_to_video` | Render a completed singular or branched NarrativeRequest without rerunning prompt generation. | | `POST /v2/external/video/text_to_interactive_video` | Build and render a branching interactive-video workflow from text. | | `POST /v2/external/video/lip_sync` | Create a lip-sync video-to-video request from public `video_url` and `audio_url` inputs. | | `POST /v2/external/video/sound_effect` | Create a sound-effect video-to-video request from a public `video_url` input and prompt. | | `GET /v2/external/video/status` | Poll by `request_id` or `session_id`. | | `GET /v2/external/video/{request_id}/status` | Path-param status polling alias. | | `GET /v2/external/video/status_detailed` | Poll detailed status plus normalized video preview data. | | `GET /v2/external/video/{request_id}/status_detailed` | Path-param detailed status polling alias. | `status` and `status_detailed` also accept `POST` with `request_id` or `session_id` in the JSON body. Hosted `SEEDANCE2.5I2V` requests use the validated GMICloud model `seedance-2-5-260628` for both the text-to-video agent's scene animation and direct external image-to-video handling. Hosted routing does not fall back to Fal, and one logical layer request is dispatched to one adapter only. On terminal video failure, both status routes return the exact stored provider or model message instead of replacing it with a generic pipeline error. Compact status exposes it through `generationError`, `expressGenerationError`, `error`, and `message`. Detailed status also retains the failed asset's error under `session.layers`, such as `aiVideoGenerationError`, `imageSession.generationError`, `lipSyncVideoGenerationError`, or `soundEffectVideoGenerationError`. Session listing, translation, joining, subtitle, reroll, clone, outro/footer, and render-control paths from the [Video API](/video) are also available through this mounted prefix. For `lip_sync` and `sound_effect`, media inputs must be publicly reachable HTTP(S) URLs. Raw bytes, base64/data URLs, local filesystem paths, localhost URLs, and private network URLs are rejected. Docker deployments should publish local media through configured S3-compatible storage, CloudFront, or another signed/public URL strategy before calling these hosted external routes. Example: ```bash curl -X POST "https://api.samsar.one/v2/external/video/text_to_video" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "prompt": "A cinematic product launch reel", "image_model": "GPTIMAGE2.5", "video_model": "RUNWAYML", "duration": 10, "aspect_ratio": "9:16", "inference_model": "gpt-6-astra" }, "webhookUrl": "https://example.com/webhook" }' ``` Image-to-video example: ```bash curl -X POST "https://api.samsar.one/v2/external/video/image_to_video" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "image_url": "https://cdn.example.com/scene-1.png", "prompt": "A polished destination reel", "video_model": "RUNWAYML", "aspect_ratio": "16:9" } }' ``` Narrative-to-video example: ```bash curl -X POST "https://api.samsar.one/v2/external/video/narrative_to_video" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "narrative_request_id": "687b4127b503e2b4acdf9876" } }' ``` The source can be singular or branched. For a branched source, polling returns a normalized `branching` manifest with one path per leaf, choice-point metadata, path-specific frame/video state, and aggregate progress. The compact status response avoids raw frame data; `status_detailed` adds path timelines that reference each shared layer and audio asset by ID for client-side preview. The request reaches top-level `COMPLETED` only after every leaf video has rendered. The terminal manifest exposes the complete `branching.outputs.paths` URL mapping, while compatibility fields keep `result_url` as the default path, return every leaf in ordered `result_urls`, and retain `branch_results`. See [Render a narrative as video](/narrative/render-video) for the full lifecycle, normalized response, and interactive switching flow. Lip-sync example: ```bash curl -X POST "https://api.samsar.one/v2/external/video/lip_sync" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "video_url": "https://cdn.example.com/clip.mp4", "audio_url": "https://cdn.example.com/dialogue.wav", "lip_sync_model": "SYNCLIPSYNC", "duration": 8, "prompt": "Match the speaker performance to the supplied audio." } }' ``` Sound-effect example: ```bash curl -X POST "https://api.samsar.one/v2/external/video/sound_effect" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "video_url": "https://cdn.example.com/clip.mp4", "prompt": "Soft ocean waves and distant city ambience", "sound_effect_model": "MIRELOAI", "duration": 8 } }' ``` Poll: ```bash curl "https://api.samsar.one/v2/external/video/status?request_id=66ff..." \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` Detailed status: ```bash curl "https://api.samsar.one/v2/external/video/status_detailed?request_id=66ff..." \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` For branched video sessions, read `branching.summary` to display progress and `branching.paths` to display each leaf render. In detailed status, build maps of `session.layers` and `session.audioLayers` by `id`, then resolve each selected path's `timeline[].layer_id` and `audio_timeline[].audio_layer_id` references to preview its current assets. At a `branching.tree.choice_points[].switch_at_seconds` boundary, the selected option's `leaf_path_ids` narrows the candidate final videos; `selection_trail` provides the exact ordered choices for each leaf. ## Audio Hosted audio is asynchronous. Submit one of the canonical routes below, persist the returned `request_id`, then poll `/v2/external/audio/status`. | Route | Model/provider values | Price | | --- | --- | ---: | | `POST /v2/external/audio/text_to_speech` | `OPENAI`, `ELEVENLABS`, `GOOGLE`, `PLAYAI` | 1 credit/request | | `POST /v2/external/audio/text_to_music` | `ELEVENLABS_MUSIC`, `LYRIA3` | 2 credits/request | | `POST /v2/external/audio/text_to_sound_effect` | `SDAUDIO` and configured aliases | 1 credit/request | | `POST /v2/external/audio/transcript_align` | OpenAI `whisper-1` | Usage based; 0.9 credits/min by default | ```bash curl -X POST "https://api.samsar.one/v2/external/audio/text_to_music" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "prompt": "Warm cinematic synth backing track, instrumental only", "model": "ELEVENLABS_MUSIC", "duration": 30, "is_instrumental": true } }' ``` Poll: ```bash curl "https://api.samsar.one/v2/external/audio/status?request_id=66ff..." \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` ## samsar-js ```ts const samsar = new SamsarClient({ apiKey: process.env.SAMSAR_API_KEY! }); await samsar.validateV2ExternalSamsarApiKey(); const chat = await samsar.createV2ExternalChatCompletion({ model: 'gpt-6-astra', messages: [{ role: 'user', content: 'Write a concise product caption.' }], }); const video = await samsar.createV2ExternalVideoRequestFromText({ prompt: 'A cinematic product launch reel', image_model: 'GPTIMAGE2.5', video_model: 'RUNWAYML', duration: 10, }); const status = await samsar.getV2ExternalVideoStatus(video.data.request_id!); const imageVideo = await samsar.createV2ExternalVideoRequestFromImage({ image_url: 'https://cdn.example.com/scene-1.png', video_model: 'RUNWAYML', }); const lipSync = await samsar.createV2ExternalLipSyncVideo({ video_url: 'https://cdn.example.com/clip.mp4', audio_url: 'https://cdn.example.com/dialogue.wav', lip_sync_model: 'SYNCLIPSYNC', }); const soundEffect = await samsar.createV2ExternalSoundEffectVideo({ video_url: 'https://cdn.example.com/clip.mp4', prompt: 'Soft ocean waves and distant city ambience', sound_effect_model: 'MIRELOAI', }); const detailed = await samsar.getV2ExternalVideoStatusDetailed(lipSync.data.request_id!); await samsar.requestV2ExternalImage('enhance', { image_url: 'https://cdn.example.com/photo.png', resolution: '1k', }); const music = await samsar.createV2ExternalTextToMusicAudio({ prompt: 'Warm cinematic synth backing track, instrumental only', model: 'ELEVENLABS_MUSIC', duration: 30, is_instrumental: true, }); const audioStatus = await samsar.getV2ExternalAudioStatus(music.data.request_id!); ``` The shorter aliases also use the v2 external routes: - `validateSamsarApiKey` - `getExternalProviderCapabilities` - `validateDeploymentProviders` - `createExternalChat` - `createExternalChatCompletion` - `requestExternalImage` - `requestExternalVideo` - `createExternalVideoRequestFromText` - `createExternalVideoRequestFromImageList` - `createExternalVideoRequestFromImage` - `createExternalLipSyncVideo` - `createExternalSoundEffectVideo` - `getExternalImageStatus` - `getExternalVideoStatus` - `getExternalVideoStatusDetailed` - `getExternalVideoDetailedStatus` - `requestExternalAudio` - `requestExternalAudioRoute` - `createExternalTextToMusicAudio` - `getExternalAudioStatus` CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/external-users-api.mdx METADATA: title: External Users API description: Attribute shared Samsar usage to external platform users with scoped credits, login tokens, and request history. image: img/og/external-users-api.png sidebar_position: 5 slug: /external-users sidebar_label: External Users API PRIMARY_HEADING: External Users API CONTENT_START # External Users API **Quick facts** - Base URL: `https://api.samsar.one/v1` - Route prefix: `/external_users` - Authentication: `Platform key, scoped external key, or exchanged client token` - Execution: `Synchronous controls plus asynchronous generation` - Billing: `Credits, refunds, history, and purchases scoped per external user` - Content type: `application/json` Use this surface when many users share one central Samsar account, but each external user still needs their own credits, request history, payments, publishing controls, and login session. ## External user identity Most external endpoints accept an `external_user` object: ```json { "external_user": { "provider": "whop", "unique_key": "whop:usr_123", "external_user_id": "usr_123", "external_app_id": "app_abc", "external_company_id": "biz_456", "email": "member@example.com", "username": "member_handle", "display_name": "Member Name", "metadata": { "membership_id": "mem_123" } } } ``` Required fields: - `provider` - `external_user_id` Optional stable reference: - `unique_key`: a globally unique reference for the external user, such as a wallet address, email address, or platform user key. If omitted, Samsar stores `external_user_id` as the `unique_key`. Recommended for multi-app providers: - `external_app_id` - `external_company_id` The first successful bootstrap request upserts an `ExternalUser` record mapped to the owning Samsar account internally. The stored record keeps both the owning internal user id and the external user's `unique_key`. For V2 clients that only need to register and reference an external user by a single key, use `POST /v2/user/create_external_user` with an internal API key, user auth token, or APP_KEY: ```bash curl -X POST https://api.samsar.one/v2/user/create_external_user \ -H "Authorization: Bearer YOUR_PLATFORM_API_KEY_****************" \ -H "Content-Type: application/json" \ -d '{ "unique_key": "wallet:0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "email": "member@example.com", "display_name": "Member Name" }' ``` The response includes `external_user.unique_key` and a `reference` object. Future V2 calls can pass that `unique_key` to resolve the external user. ## Auth model There are two supported auth patterns. ### 1. Server-to-server platform flow 1. Your backend calls `POST /external_users/session` with the shared platform API key plus an `external_user` object. 2. Samsar upserts the external user and returns an `external_api_key`. 3. Subsequent external-user scoped requests include: - `Authorization: Bearer ` - `x-external-user-api-key: ` ### 2. Client login flow 1. Your backend calls `POST /external_users/create_login_token`. 2. Samsar returns a short-lived `loginToken` and a ready-to-open `loginUrl`. 3. The client exchanges that `loginToken` through `GET /users/verify_token?loginToken=...`. 4. The response contains a long-lived `authToken`. 5. The client can call `/v1/external_users/*` routes with: - `Authorization: Bearer ` The platform API key should stay server-side. Only the external auth token should reach the browser. ## Response conventions External generation endpoints return external request ids: ```json { "request_id": "extreq_...", "session_id": "extreq_...", "external_request_id": "extreq_...", "external_session_id": "extreq_...", "upstream_request_id": "66ff...", "upstream_session_id": "66ff...", "status_endpoint": "/v1/external_users/status?request_id=extreq_..." } ``` - `request_id` / `session_id` are the external ids you should store client-side. - `upstream_request_id` / `upstream_session_id` are the internal Samsar session ids. - Poll `GET /external_users/status` with the external request id. ## POST /external_users/session Create or refresh an external-user session and receive that user’s dedicated API key plus current credits. ```bash curl -X POST https://api.samsar.one/v1/external_users/session \ -H "Authorization: Bearer YOUR_PLATFORM_API_KEY_****************" \ -H "Content-Type: application/json" \ -d '{ "external_user": { "provider": "whop", "unique_key": "whop:usr_123", "external_user_id": "usr_123", "external_app_id": "app_abc" } }' ``` Success response: ```json { "external_api_key": "sxu_********************************", "remainingCredits": 1000, "external_user": { "provider": "whop", "unique_key": "whop:usr_123", "external_user_id": "usr_123", "generation_credits": 1000, "has_external_api_key": true } } ``` ## POST /external_users/create_login_token Create a short-lived external-user login token and a ready-to-open client login URL. ```bash curl -X POST https://api.samsar.one/v1/external_users/create_login_token \ -H "Authorization: Bearer YOUR_PLATFORM_API_KEY_****************" \ -H "x-external-user-api-key: YOUR_EXTERNAL_USER_API_KEY_****************" \ -H "Content-Type: application/json" \ -d '{ "redirect": "/external/studio" }' ``` Success response: ```json { "loginToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expiresInSeconds": 600, "expiresAt": "2026-03-21T12:00:00.000Z", "loginUrl": "https://app.samsar.one/verify?loginToken=...&redirect=%2Fexternal%2Fstudio", "external_user": { "provider": "whop", "external_user_id": "usr_123" } } ``` ## GET /users/verify_token Exchange the external `loginToken` for a long-lived external `authToken`. ```bash curl "https://api.samsar.one/users/verify_token?loginToken=LOGIN_TOKEN_****************" ``` Success response: ```json { "_id": "69bbb64111f47422c1083360", "authToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "isExternalUser": true, "provider": "whop", "externalUserId": "user_46cF7yVLkPms5", "externalAppId": "app_8jnV5WOvSLgyLr", "generationCredits": 3000 } ``` Once you have this `authToken`, call external-user routes with: ```bash -H "Authorization: Bearer EXTERNAL_AUTH_TOKEN_****************" ``` ## POST /external_users/assistant/set_system_prompt Store or clear an assistant system prompt for one external user. This prompt is applied to that external user’s future assistant requests and overrides the owning account prompt for that external user only. ```bash curl -X POST https://api.samsar.one/v1/external_users/assistant/set_system_prompt \ -H "Authorization: Bearer YOUR_PLATFORM_API_KEY_****************" \ -H "x-external-user-api-key: YOUR_EXTERNAL_USER_API_KEY_****************" \ -H "Content-Type: application/json" \ -d '{ "system_prompt": "You are the storefront assistant for this creator. Keep answers concise, visually aware, and commercially practical." }' ``` Success response: ```json { "system_prompt": "You are the storefront assistant for this creator. Keep answers concise, visually aware, and commercially practical.", "model": "gpt-6-astra", "selected_assistant_model": "gpt-6-astra", "external_user": { "provider": "whop", "external_user_id": "usr_123" } } ``` Pass `null` or an empty string to clear the external-user-specific prompt and fall back to the owning account prompt or Samsar’s minimal default prompt. ## POST /external_users/assistant/completion Create a synchronous assistant response for an external user. Credits are deducted from the external user balance, while the owning Samsar account’s configured assistant model is used internally. - `session_id` is required. - You can send the external request/session id returned by external generation routes, or the upstream internal session id. - Request and response format match the standard Assistant API, including multimodal input and image-generation tool calls. - Pricing follows the main Assistant API: actual token usage is converted with **100 credits = $1** and multiplied by **1.5x**. ```bash curl -X POST https://api.samsar.one/v1/external_users/assistant/completion \ -H "Authorization: Bearer YOUR_PLATFORM_API_KEY_****************" \ -H "x-external-user-api-key: YOUR_EXTERNAL_USER_API_KEY_****************" \ -H "Content-Type: application/json" \ -d '{ "session_id": "extreq_123", "input": "Write a product caption for this video and suggest a headline.", "max_output_tokens": 250 }' ``` Success response: ```json { "id": "resp_123", "object": "response", "created_at": 1773651000, "status": "completed", "model": "gpt-6-astra", "output_text": "Built to move fast: bold design, clean energy, and a launch-ready look in one line.", "output": [ { "id": "resp_123_message", "type": "message", "role": "assistant", "content": [ { "type": "output_text", "text": "Built to move fast: bold design, clean energy, and a launch-ready look in one line.", "annotations": [] } ] } ], "usage": { "input_tokens": 482, "output_tokens": 43, "total_tokens": 525 } } ``` Response headers: - `x-credits-charged` - `x-credits-remaining` ## POST /external_users/generate_embeddings_from_plain_text Create embeddings for one external user from already cleaned plain text. This route skips crawling entirely and uses the same compatible embedding template format as the main chat embedding endpoints. - Request body accepts the same cleaned text shapes as `/chat/generate_embeddings_from_plain_text`. - External-user credits are charged on the external balance only. - Pricing is the embedding token cost with a **2.5x** multiplier and no crawl charges. ```bash curl -X POST https://api.samsar.one/v1/external_users/generate_embeddings_from_plain_text \ -H "Authorization: Bearer YOUR_PLATFORM_API_KEY_****************" \ -H "x-external-user-api-key: YOUR_EXTERNAL_USER_API_KEY_****************" \ -H "Content-Type: application/json" \ -d '{ "input": { "name": "product-docs-clean", "plain_text": [ { "url": "https://example.com/docs/getting-started", "title": "Getting Started", "content": "Cleaned plain text content from the page." } ] } }' ``` Success response: ```json { "template_id": "66f3b0d64c8bca9c6f2bd1a3", "template_hash": "b0e6f2d2c2f1d0c4d9f7f3a1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9", "hash_link": "embedding_template:b0e6f2d2c2f1d0c4d9f7f3a1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9", "record_count": 1, "structured_fields": [ { "key": "hostname", "type": "string" } ], "unstructured_fields": ["title", "content"], "external_user": { "provider": "whop", "external_user_id": "usr_123", "generation_credits": 980 } } ``` Response headers: - `x-credits-charged` - `x-credits-remaining` ## POST /external_users/text_to_video Create a text-to-video request attributed to an external user. ```bash curl -X POST https://api.samsar.one/v1/external_users/text_to_video \ -H "Authorization: Bearer YOUR_PLATFORM_API_KEY_****************" \ -H "x-external-user-api-key: YOUR_EXTERNAL_USER_API_KEY_****************" \ -H "Content-Type: application/json" \ -d '{ "external_user": { "provider": "whop", "external_user_id": "usr_123", "external_app_id": "app_abc" }, "input": { "prompt": "A cinematic teaser for a new sneaker drop", "image_model": "GPTIMAGE2.5", "video_model": "RUNWAYML", "duration": 10, "tone": "grounded", "aspect_ratio": "16:9", "language": "en", "enable_subtitles": true, "font_key": "Poppins", "generate_outro_image": true, "cta_url": "https://example.com/drop", "cta_text_top": "Scan to shop", "cta_text_bottom": "Limited release", "add_footer_animation": true, "footer_metadata": [ { "url": "https://example.com/drop", "title": "Shop the drop" } ] } }' ``` `input` uses the same fields, current model keys, and validation rules as [`POST /video/text_to_video`](/video#post-videotext_to_video). The external-user route adds scoped identity and billing: credits are reserved from that external user's balance after validation, then unused credits are refunded if the request is cancelled, fails, or finishes below the provisional estimate. ## POST /external_users/upload_image_data Upload image data URLs for an external user before calling `image_list_to_video`. ```json { "external_user": { "provider": "whop", "external_user_id": "usr_123", "external_app_id": "app_abc" }, "input": { "image_data": [ "data:image/png;base64,..." ] } } ``` Success response: ```json { "image_urls": [ "https://static.samsar.one/..." ] } ``` ## POST /external_users/image_list_to_video Create a video from an ordered list of images attributed to an external user. ```bash curl -X POST https://api.samsar.one/v1/external_users/image_list_to_video \ -H "Authorization: Bearer YOUR_PLATFORM_API_KEY_****************" \ -H "x-external-user-api-key: YOUR_EXTERNAL_USER_API_KEY_****************" \ -H "Content-Type: application/json" \ -d '{ "external_user": { "provider": "whop", "external_user_id": "usr_123", "external_app_id": "app_abc" }, "input": { "image_urls": [ "https://cdn.example.com/frame1.png", "https://cdn.example.com/frame2.png" ], "prompt": "Fast product reveal pacing", "video_model": "RUNWAYML", "aspect_ratio": "9:16", "metadata": { "campaign": "drop-07" }, "language": "en", "enable_subtitles": true, "font_key": "Poppins", "limit_single_narrator": false, "add_narrator_avatar": false } }' ``` `input` uses the same image-list contract as [`POST /video/image_list_to_video`](/video#post-videoimage_list_to_video), including current image, video, inference, music, TTS, subtitle, outro, footer, and narrator options. External-user differences are limited to scoped identity, credit reservation/refunds, and request history. The route defaults to `RUNWAYML` when `video_model` is omitted and supports `16:9` or `9:16`. Billing uses the central video model rates; narrator avatars add **4 credits/sec** and express CTA generation adds **1 credit/sec**. Response headers report the accepted charge and remaining external-user balance when available. ## GET /external_users/status Poll an external request until completion. ```bash curl "https://api.samsar.one/v1/external_users/status?request_id=extreq_123" \ -H "Authorization: Bearer YOUR_PLATFORM_API_KEY_****************" \ -H "x-external-user-api-key: YOUR_EXTERNAL_USER_API_KEY_****************" ``` Success response: ```json { "request_id": "extreq_123", "session_id": "extreq_123", "external_request_id": "extreq_123", "upstream_session_id": "66ff...", "status": "COMPLETED", "result_url": "https://static.samsar.one/...", "has_subtitles": true, "result_language": "en", "creditsCharged": 750, "creditsRefunded": 0, "remainingCredits": 4250 } ``` Completed video responses include `has_subtitles` and `result_language` when the upstream video session has that metadata. If a generation fails or completes with a refund, the external request record is updated to reflect the refund. ## GET /external_users/requests Fetch recent render requests for one external user. ```bash curl "https://api.samsar.one/v1/external_users/requests?provider=whop&external_user_id=usr_123&external_app_id=app_abc&limit=12" \ -H "Authorization: Bearer YOUR_PLATFORM_API_KEY_****************" \ -H "x-external-user-api-key: YOUR_EXTERNAL_USER_API_KEY_****************" ``` Success response: ```json { "requests": [ { "request_id": "extreq_123", "route_key": "text_to_video", "status": "COMPLETED", "prompt": "A cinematic teaser for a new sneaker drop", "video_url": "https://static.samsar.one/...", "credits_charged": 750, "credits_refunded": 0, "remaining_credits": 4250, "is_published": true, "published_title": "Sneaker teaser" } ], "external_user": { "provider": "whop", "external_user_id": "usr_123" } } ``` ## POST /external_users/publish Publish a completed external-user request to the public library/feed. ```bash curl -X POST https://api.samsar.one/v1/external_users/publish \ -H "Authorization: Bearer YOUR_PLATFORM_API_KEY_****************" \ -H "x-external-user-api-key: YOUR_EXTERNAL_USER_API_KEY_****************" \ -H "Content-Type: application/json" \ -d '{ "request_id": "extreq_123", "title": "Sneaker teaser", "description": "Launch-day vertical cut", "tags": ["launch", "footwear"] }' ``` Success response: ```json { "request": { "request_id": "extreq_123", "is_published": true, "published_title": "Sneaker teaser" }, "publication": { "_id": "pub_123" } } ``` ## POST /external_users/archive Archive an external-user request. This costs no credits and also removes the video from publications if it was already published. ```bash curl -X POST https://api.samsar.one/v1/external_users/archive \ -H "Authorization: Bearer YOUR_PLATFORM_API_KEY_****************" \ -H "x-external-user-api-key: YOUR_EXTERNAL_USER_API_KEY_****************" \ -H "Content-Type: application/json" \ -d '{ "request_id": "extreq_123" }' ``` Success response: ```json { "request": { "request_id": "extreq_123", "status": "ARCHIVED" } } ``` ## GET /external_users/credits Fetch the remaining credits for one external user plus their attribution summary. ```bash curl "https://api.samsar.one/v1/external_users/credits?provider=whop&external_user_id=usr_123&external_app_id=app_abc" \ -H "Authorization: Bearer YOUR_PLATFORM_API_KEY_****************" \ -H "x-external-user-api-key: YOUR_EXTERNAL_USER_API_KEY_****************" ``` Success response: ```json { "remainingCredits": 4920, "lastTopUp": { "amountPaidCents": 5000, "creditsApplied": 5000 }, "externalUser": { "provider": "whop", "external_user_id": "usr_123", "generation_credits": 4920, "total_requests": 4, "total_credits_used": 1820, "total_credits_refunded": 150, "total_credits_purchased": 5000 } } ``` ## POST /external_users/credits/grant Manually grant credits to an external user. This also credits the shared platform account internally so upstream requests stay in sync. ```bash curl -X POST https://api.samsar.one/v1/external_users/credits/grant \ -H "Authorization: Bearer YOUR_PLATFORM_API_KEY_****************" \ -H "Content-Type: application/json" \ -d '{ "external_user": { "provider": "whop", "external_user_id": "usr_123", "external_app_id": "app_abc" }, "input": { "credits": 1000 } }' ``` Success response: ```json { "creditsGranted": 1000, "remainingCredits": 1000, "internalRemainingCredits": 12000, "externalUser": { "provider": "whop", "external_user_id": "usr_123", "generation_credits": 1000 } } ``` ## POST /external_users/credits/recharge Create a Stripe checkout link for an external-user credit purchase. ```json { "external_user": { "provider": "whop", "external_user_id": "usr_123", "external_app_id": "app_abc" }, "input": { "credits": 2500 } } ``` Success response: ```json { "external_payment_id": "extpay_123", "url": "https://checkout.stripe.com/c/pay/...", "checkoutSessionId": "cs_test_123", "paymentIntentId": "pi_123", "paymentStatusEndpoint": "/v1/external_users/payment_status", "credits": 2500, "amountUsd": 25, "amountCents": 2500, "currency": "USD" } ``` ## GET /external_users/payment_status Poll the status of an external-user-attributed recharge. ```bash curl "https://api.samsar.one/v1/external_users/payment_status?external_payment_id=extpay_123" \ -H "Authorization: Bearer YOUR_PLATFORM_API_KEY_****************" \ -H "x-external-user-api-key: YOUR_EXTERNAL_USER_API_KEY_****************" ``` Success response: ```json { "external_payment_id": "extpay_123", "status": "succeeded", "mode": "payment", "checkoutSessionId": "cs_test_123", "paymentIntentId": "pi_123", "amountCents": 2500, "currency": "usd", "remainingCredits": 5000 } ``` When payment succeeds, credits are applied to the external user’s `generationCredits`. The underlying platform API key is not returned to the caller. CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/fonts.mdx METADATA: title: Fonts description: Preview supported subtitle fonts by language. sidebar_label: Fonts slug: /fonts PRIMARY_HEADING: Fonts CONTENT_START # Fonts Use these previews to choose subtitle fonts by language. Font keys are shown inside each image and match the values returned by `GET /supported_fonts`. Samples automatically switch between light and dark assets based on the docs theme. ## Default (default) {#default} - **Rampart One** - **Montserrat** - **Arial** - **sans-serif** ## English (en) {#en} - **Poppins** - **Montserrat** - **Arial** - **sans-serif** ## French (fr) {#fr} - **Poppins** - **Montserrat** - **Arial** - **sans-serif** ## Spanish (es) {#es} - **Rampart One** - **Montserrat** - **Arial** - **sans-serif** ## German (de) {#de} - **Rampart One** - **Montserrat** - **Arial** - **sans-serif** ## Portuguese (pt) {#pt} - **Rampart One** - **Montserrat** - **Arial** - **sans-serif** ## Italian (it) {#it} - **Rampart One** - **Montserrat** - **Arial** - **sans-serif** ## Dutch (nl) {#nl} - **Rampart One** - **Montserrat** - **Arial** - **sans-serif** ## Swedish (sv) {#sv} - **Rampart One** - **Montserrat** - **Arial** - **sans-serif** ## Russian (ru) {#ru} - **Arial** - **sans-serif** ## Japanese (ja) {#ja} - **Noto Sans JP** - **M PLUS Rounded 1c** - **Hiragino Sans** - **Yu Gothic UI** - **sans-serif** ## Korean (ko) {#ko} - **Noto Sans KR** - **Pretendard** - **Apple SD Gothic Neo** - **Malgun Gothic** - **sans-serif** ## Thai (th) {#th} - **Sarabun** - **Tahoma** - **Leelawadee UI** - **sans-serif** ## Arabic (ar) {#ar} - **Noto Sans Arabic** - **Cairo** - **Geeza Pro** - **Segoe UI** - **sans-serif** ## Bengali (bn) {#bn} - **Noto Sans Bengali** - **Hind Siliguri** - **Noto Serif Bengali** - **sans-serif** ## Hindi (hi) {#hi} - **Noto Sans Devanagari** - **Mukta** - **Hind** - **Noto Serif Devanagari** - **Kohinoor Devanagari** - **sans-serif** ## Hebrew (he) {#he} - **Noto Sans Hebrew** - **Rubik** - **Arial Hebrew** - **sans-serif** ## Latin (la) {#la} - **Rampart One** - **Montserrat** - **Arial** - **sans-serif** ## Sanskrit (sa) {#sa} - **Noto Sans Devanagari** - **Mukta** - **Hind** - **Noto Serif Devanagari** - **Kohinoor Devanagari** - **sans-serif** ## Chinese (zh) {#zh} - **Noto Sans SC** - **Source Han Sans SC** - **PingFang SC** - **Microsoft YaHei** - **sans-serif** ## Chinese (Simplified) (zh-cn) {#zh-cn} - **Noto Sans SC** - **Source Han Sans SC** - **PingFang SC** - **Microsoft YaHei** - **sans-serif** ## Chinese (Traditional) (zh-tw) {#zh-tw} - **Noto Sans TC** - **Source Han Sans TC** - **PingFang TC** - **Microsoft JhengHei** - **sans-serif** CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/image-api.mdx METADATA: title: Image API description: Enhance, edit, white-label, validate receipts, and manage generated image sets with SamsarOne's image endpoints. image: img/og/image-api.png sidebar_position: 2 slug: /image-api sidebar_label: Image API PRIMARY_HEADING: Image API CONTENT_START # Image API Compare [image generation](/models?modality=image) and [image editing](/models?modality=image-edit) models, including their supported adapters. Start with hosted **samsar-js** and use the [deployment guide](/adapters) when configuring your own providers. Run enhance or branding removal calls in the Playground and poll their status before wiring them into code. **Quick facts** - Base URL: `https://api.samsar.one/v1` - Authentication: `Bearer API key` - Execution: `Generation and edits are async; title assignment is synchronous` - Billing: `Route-specific; credit headers report usage` - Content type: `application/json` Poll `/image/status?request_id=...` (or `/v1/status`) for asynchronous work, or use `/image/list` to fetch history. ## Pricing | Endpoint | Credits | Notes | | ------------------------ | ------------------------------------------------- | ------------------------------------------------------------- | | `POST /image/text_to_image` | `15 × num_images` | Text-to-image generation; defaults to `NANOBANANA2`. | | `POST /image/assign_title` | Usage-based credits | Image title generation. Returns synchronously. | | `POST /image/enhance` | 11 (`0.5k`) / 15 (`1k`) / 22 (`2k`) / 29 (`4k`) | Based on `resolution` (defaults to `1k`). | | `POST /image/remove_branding` | 15 | Remove visible text from images for white-label marketplace listings. | | `POST /image/add_image_set` | `15 × requested_images` | `requested_images = num_images`. | | `POST /image/receipt_templates/create` | Free | Builds and saves a receipt template + normalized schema from one sample image. | | `GET /image/template_json` | Free | Returns the saved structured template JSON for a `template_id` owned by your API key. | | `POST /image/receipt_templates/query` | 50 | Validates receipt against template and returns standardized JSON fields. | | `POST /image/create_rollup_banner` | 30 | Flat per request. | | `POST /image/enhance_and_generate_rollup_banner` | Variable | Enhance credits for low-res images + 30 credits per rollup request. | ## POST /image/text_to_image Generate one or more images from a text prompt. This route is also available as `/image/generate` and `/image/generations`. Use `GPTIMAGE2.5` for **GPTImage 2.5**. The retired key `GPTIMAGE2` is not an alias: new requests return HTTP 400 with `Invalid model: GPTIMAGE2. Use GPTIMAGE2.5.` This validation also applies to the `mode` field and the v2 image-generation routes. ```json { "prompt": "A minimal product still life on warm stone, soft daylight.", "model": "NANOBANANA2", "aspect_ratio": "1:1", "num_images": 1 } ``` | Field | Required | Notes | | --- | --- | --- | | `prompt` | Yes | Non-empty generation prompt. | | `model` or `mode` | No | `GPTIMAGE2.5`, `NANOBANANA2` (default), `NANOBANANAPRO`, `SEEDREAM`, or `WAN2.7PRO`. | | `aspect_ratio` or `aspectRatio` | No | Defaults to `1:1`; Wan 2.7 Pro accepts `1:1`, `16:9`, or `9:16`. | | `resolution` | No | Wan 2.7 Pro accepts `1K`; other models ignore this field. | | `num_images` or `numImages` | No | Positive output count; defaults to `1`. Billing is 15 credits per output. | **Success response (200)** ```json { "status": "queued", "request_id": "img_gen_123", "session_id": "img_gen_123", "case_type": "text_to_image", "model": "NANOBANANA2", "aspect_ratio": "1:1", "num_images": 1, "creditsCharged": 15, "remainingCredits": 4985 } ``` Poll `GET /image/status?request_id=img_gen_123` until the request is complete. ## POST /image/assign_title Assign a short SEO-friendly title to an image. The title is suitable for an image title or for client-side filename generation after camel-casing. This endpoint uses two generation steps: 1. Analyze the image in low-detail vision mode for naming-relevant visual cues. 2. Combine that description with optional metadata JSON to create one short title. **JSON request body** ```json { "image_url": "https://cdn.example.com/product-photo.png", "metadata": { "product": "linen travel shirt", "collection": "spring essentials" } } ``` You can also send `image_data` as a base64 image data URL. **Multipart request** ```bash curl -X POST "https://api.samsar.one/v1/image/assign_title" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -F "image=@./product-photo.png" \ -F 'metadata={"product":"linen travel shirt","collection":"spring essentials"}' ``` Supported image file types are PNG, JPEG, WEBP, and non-animated GIF. **Success response (200)** ```json { "content": "Linen Travel Shirt" } ``` Response headers include: - `x-credits-charged: ` - `x-credits-remaining: ` ## POST /image/enhance Upscale and denoise an image. **Request body** ```json { "image_url": "https://cdn.example.com/photo.png", "resolution": "0.5k | 1k | 2k | 4k" } ``` - `image_url` required, non-empty URL. - `resolution` optional; defaults to `1k`. **Success response (200)** ```json { "status": "queued", "request_id": "img_enh_123", "session_id": "img_enh_123", "global_status_id": "img_enh_123", "case_type": "image_enhance", "image_url": "https://cdn.example.com/photo.png", "resolution": "1K", "aspect_ratio": "16:9", "creditsCharged": 15, "remainingCredits": 4985 } ``` ## POST /image/remove_branding Remove visible text from an image. **Request body** ```json { "image_url": "https://cdn.example.com/watermarked.png" } ``` **Success response (200)** ```json { "status": "queued", "request_id": "img_rm_123", "session_id": "img_rm_123", "global_status_id": "img_rm_123", "case_type": "logo_remove", "image_url": "https://cdn.example.com/watermarked.png", "creditsCharged": 15, "remainingCredits": 4985 } ``` ## POST /image/add_image_set Extend or save a set of images for downstream generation. **Request body** ```json { "image_urls": [ "https://cdn.example.com/look1.png", "https://cdn.example.com/look2.png" ], "metadata": { "campaign": "spring" }, "prompt": "Optional creative direction", "num_images": 4 } ``` - `image_urls` required, non-empty array of strings. - `num_images` optional positive number; defaults to `image_urls.length` when omitted. - `prompt` optional string; `metadata` optional object. **Success response (200)** ```json { "status": "queued", "request_id": "img_set_123", "session_id": "img_set_123", "global_status_id": "img_set_123", "case_type": "image_list_to_image_set", "image_urls": [ "https://cdn.example.com/look1.png", "https://cdn.example.com/look2.png" ], "metadata": { "campaign": "spring" }, "prompt": "Optional creative direction", "num_images": 4, "creditsCharged": 60, "remainingCredits": 4940 } ``` ## POST /image/receipt_templates/create Create and persist a reusable receipt extraction template from a sample receipt image. This endpoint is free (no credits deducted). **Request body** ```json { "image_url": "https://cdn.example.com/receipt-template.png", "template_name": "kbank-transfer-template" } ``` - `image_url` required, public image URL. - `template_name` optional. **Success response (200)** ```json { "template_id": "receipt_tpl_0e4f9f1dca744ec9b3a3f8df", "template_hash": "f8b8f4e6e7d9b66d3f4a9e6f35b17c4d7ac4dce2f0d63f15e2d55b9872e3be35", "template_name": "kbank-transfer-template", "normalized_template": { "schema_version": "1.0", "merchant_hint": "Kasikornbank", "currency_hint": "THB", "rois": [ { "id": "meta_header", "label": "Header and Meta", "left": 0, "top": 0, "width": 1, "height": 0.35 }, { "id": "line_items", "label": "Line Items/Table", "left": 0, "top": 0.28, "width": 1, "height": 0.5 }, { "id": "totals", "label": "Totals", "left": 0.45, "top": 0.7, "width": 0.55, "height": 0.3 } ], "fields": [ { "key": "merchant_name", "type": "string", "required": true }, { "key": "transaction_date", "type": "date", "required": true }, { "key": "total", "type": "currency", "required": true } ], "validation_rules": { "arithmetic": true, "amount_tolerance": 0.05 } }, "sample_receipt": { "merchant_name": "Kasikornbank", "transaction_date": "2026-01-14", "total": 786 } } ``` ## POST /image/receipt_templates/query Extract receipt fields by validating an input receipt image against an existing template. This endpoint charges **50 credits** per request. **Request body** ```json { "template_id": "receipt_tpl_0e4f9f1dca744ec9b3a3f8df", "image_url": "https://cdn.example.com/receipt-instance.png" } ``` - `template_id` required. - `image_url` required. - The template must belong to the same API key. Cross-account template access returns `404`. **Success response (200)** ```json { "template_id": "receipt_tpl_0e4f9f1dca744ec9b3a3f8df", "template_hash": "f8b8f4e6e7d9b66d3f4a9e6f35b17c4d7ac4dce2f0d63f15e2d55b9872e3be35", "template_name": "kbank-transfer-template", "receipt_json": { "merchant_name": "Kasikornbank", "transaction_date": "2026-01-14", "transaction_id": "TRBS260114764844309", "currency": "THB", "fee": 0, "total": 786 }, "standardized_receipt": { "merchant_name": "Kasikornbank", "transaction_date": "2026-01-14", "transaction_time": "17:25", "transaction_id": "TRBS260114764844309", "currency": "THB", "subtotal": 786, "tax": null, "fee": 0, "discount": null, "total": 786, "payment_method": "transfer", "from_account": "xxx-x-xxxx-x", "to_account": "xxx-x-xxxx-x" }, "items": [], "unreadable_fields": [], "confidence": 0.93, "validation": { "is_valid": true, "issues": [] }, "attempts": 1 } ``` Response headers include: - `x-credits-charged: 50` - `x-credits-remaining: ` ## GET /image/template_json Fetch a saved structured receipt template by `template_id`. This endpoint validates ownership against your API key and returns the template JSON only when the template belongs to that user. Primary route: `GET /image/template_json`
Alias route: `GET /image/receipt_templates/template_json` **Query params** - `template_id` required. **Sample request** ```bash curl "https://api.samsar.one/v1/image/template_json?template_id=receipt_tpl_0e4f9f1dca744ec9b3a3f8df" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` **Success response (200)** ```json { "template_id": "receipt_tpl_0e4f9f1dca744ec9b3a3f8df", "template_hash": "f8b8f4e6e7d9b66d3f4a9e6f35b17c4d7ac4dce2f0d63f15e2d55b9872e3be35", "template_name": "kbank-transfer-template", "source_image_url": "https://cdn.example.com/receipt-template.png", "normalized_template": { "schema_version": "1.0", "merchant_hint": "Kasikornbank", "currency_hint": "THB", "rois": [ { "id": "meta_header", "label": "Header and Meta", "left": 0, "top": 0, "width": 1, "height": 0.35 }, { "id": "line_items", "label": "Line Items/Table", "left": 0, "top": 0.28, "width": 1, "height": 0.5 }, { "id": "totals", "label": "Totals", "left": 0.45, "top": 0.7, "width": 0.55, "height": 0.3 } ], "fields": [ { "key": "merchant_name", "type": "string", "required": true }, { "key": "transaction_date", "type": "date", "required": true }, { "key": "total", "type": "currency", "required": true } ], "validation_rules": { "arithmetic": true, "amount_tolerance": 0.05 } }, "template_json": { "schema_version": "1.0" }, "sample_receipt": { "merchant_name": "Kasikornbank", "transaction_date": "2026-01-14", "total": 786 }, "created_at": "2026-03-01T00:26:29.478Z", "updated_at": "2026-03-01T00:26:29.478Z" } ``` - `template_json` mirrors `normalized_template` and is returned as a compatibility alias. **Error responses** - `400` missing `template_id`. - `401` invalid API key. - `404` template does not exist or does not belong to this API key. ### JavaScript SDK (samsar-js) ```ts const samsar = new SamsarClient({ apiKey: process.env.SAMSAR_API_KEY! }); const templateJson = await samsar.getReceiptTemplateJson( 'receipt_tpl_0e4f9f1dca744ec9b3a3f8df', ); console.log(templateJson.data.template_json); ``` ## GET /image/status Check the latest state for a single image request (accepts `request_id` or `session_id`). ```bash curl "https://api.samsar.one/v1/image/status?request_id=img_enh_123" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` **Queued / processing** ```json { "session_id": "img_enh_123", "request_id": "img_enh_123", "status": "PENDING", "type": "image", "case_type": "image_enhance" } ``` **Completed** ```json { "session_id": "img_enh_123", "request_id": "img_enh_123", "status": "COMPLETED", "type": "image", "case_type": "image_enhance", "result_url": "https://cdn.samsar.one/images/img_enh_123.png" } ``` - `result_urls` is present when multiple assets are produced. - `thumbnail_url` is included for rollup banner completions and points to a downscaled preview. - A `404` is returned if the request does not belong to your API key or is unknown. ## GET /image/list Fetch a list of your recent image API sessions, including their current status and any available results. Use `limit` (max 500) to trim the response. ```bash curl "https://api.samsar.one/v1/image/list?limit=50" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` **Response (200)** ```json { "sessions": [ { "session_id": "img_rm_123", "request_id": "img_rm_123", "status": "COMPLETED", "type": "image", "case_type": "logo_remove", "request_type": "API", "result_url": "https://cdn.samsar.one/images/img_rm_123.png", "created_at": "2024-08-20T08:10:11.000Z", "updated_at": "2024-08-20T08:12:42.000Z" }, { "session_id": "img_set_987", "request_id": "img_set_987", "status": "PENDING", "type": "image", "case_type": "image_list_to_image_set", "request_type": "API", "created_at": "2024-08-20T08:15:55.000Z", "updated_at": "2024-08-20T08:15:55.000Z" } ] } ``` **Common errors** - `400` invalid or missing fields (empty URLs, bad resolution, non-numeric `num_images`). - `401` invalid API key. - `402` insufficient credits. - `404` unknown `request_id` (or a request that does not belong to the API key on `/image/status`). ## Utils ### POST /image/enhance_and_generate_rollup_banner Enhance low-resolution tiles (if needed) and generate a rollup banner. Images below the rollup minimum (2020x2526) are auto-upscaled using `1k`/`2k`/`4k`, then the list is expanded to 28 tiles before rendering. **Request body** ```json { "images": [ { "image_url": "https://cdn.example.com/tile-1.png", "image_text": "Rooftop Bar", "image_category": "Social", "overlay": { "top_left": "2 hours", "top_right": "City", "footer": "Rooftop Bar" } } ], "header_image_url": "https://cdn.example.com/header.png", "footer_image_url": "https://cdn.example.com/footer.png", "columns": 4, "max_tiles": 28, "image_tiling_position": { "font_key": "en", "font_family": "Poppins, Helvetica, Arial, sans-serif", "top_left": { "margin_min": 28, "margin_ratio": 0.02, "diameter_min": 220, "diameter_max": 320, "diameter_ratio": 0.215, "inner_padding_min": 12, "inner_padding_ratio": 0.08 }, "top_right": { "margin_min": 28, "margin_ratio": 0.02, "max_width_floor": 260, "min_width": 220, "padding_left": 36, "padding_right": 48, "padding_top": 30, "padding_bottom": 24, "overlay_height_min": 140 }, "bottom": { "inset_min": 32, "inset_ratio": 0.03, "offset_min": 12, "offset_ratio": 0.02, "container_margin_min": 40, "container_margin_ratio": 0.08, "text_inset_min": 18, "text_inset_ratio": 0.04, "overlay_height_min": 280 } } } ``` - `images` (or `image_list`/`image_urls`) required, non-empty array. - Items accept `image_url` (or `enhanced_url`) plus optional `image_text`, `image_category`, `image_duration`, `overlay`. - `columns` optional integer 1-6; defaults to 4. - `max_tiles` optional integer; defaults to 28. - `header_image_url` and `footer_image_url` optional. - `image_tiling_position` optional object for tile overlay layout and font overrides. Omit to use defaults. **image_tiling_position (optional)** Overrides for per-tile overlay placement and font selection. You can send only the fields you want to change. - `font_key` required if provided; must match a supported subtitle language key (e.g., `en`, `fr`, `es`, `ja`, `ko`, `zh-cn`, `zh-tw`, `hi`, `ar`). - `font_family` optional; comma-separated stack. The first font must be supported for the chosen `font_key`. - `top_left` optional object for the circular badge. - `margin_min`, `margin_ratio` - `diameter_min`, `diameter_max`, `diameter_ratio` - `inner_padding_min`, `inner_padding_ratio` - `top_right` optional object for the top-right label. - `margin_min`, `margin_ratio` - `max_width_floor`, `min_width` - `padding_left`, `padding_right`, `padding_top`, `padding_bottom` - `overlay_height_min` - `bottom` optional object for the footer label. - `inset_min`, `inset_ratio` - `offset_min`, `offset_ratio` - `container_margin_min`, `container_margin_ratio` - `text_inset_min`, `text_inset_ratio` - `overlay_height_min` All `*_ratio` values are fractions between 0 and 1. Values are validated; invalid ranges return `400`. **Success response (200)** ```json { "status": "completed", "session_id": "rollup_enh_123", "request_id": "rollup_enh_123", "rollup_session_id": "rollup_456", "rollup_request_id": "rollup_456", "case_type": "rollup_banner_enhance", "result_url": "https://cdn.samsar.one/rollups/rollup_456.png", "thumbnail_url": "https://cdn.samsar.one/rollups/rollup_456-thumb.png", "result_urls": ["https://cdn.samsar.one/rollups/rollup_456.png"], "input_image_urls": [ "https://cdn.example.com/tile-1-enhanced.png" ], "creditsCharged": 59, "remainingCredits": 4941 } ``` Poll `/image/status?request_id=rollup_enh_123` for completion. The status payload includes `case_type: rollup_banner_enhance`. `input_image_urls` returns the enhanced URLs used for the rollup request (falls back to original URLs when no enhancement was needed), before any duplication to 28 tiles. ### POST /image/create_rollup_banner Generate a rollup banner image from a list of tiles, with optional header/footer imagery. **Request body** ```json { "image_list": [ { "image_url": "https://cdn.example.com/tile-1.png", "title": "Rooftop Bar", "image_category": "Social", "image_duration": "2 hours" }, { "enhanced_url": "https://cdn.example.com/tile-2.png", "overlay": { "top_left": "Half day", "top_right": "Pool", "footer": "Cabana" } } ], "header_image_url": "https://cdn.example.com/header.png", "footer_image_url": "https://cdn.example.com/footer.png", "columns": 4, "max_tiles": 28, "image_tiling_position": { "font_key": "en", "font_family": "Poppins, Helvetica, Arial, sans-serif", "top_left": { "margin_min": 28, "margin_ratio": 0.02, "diameter_min": 220, "diameter_max": 320, "diameter_ratio": 0.215, "inner_padding_min": 12, "inner_padding_ratio": 0.08 }, "top_right": { "margin_min": 28, "margin_ratio": 0.02, "max_width_floor": 260, "min_width": 220, "padding_left": 36, "padding_right": 48, "padding_top": 30, "padding_bottom": 24, "overlay_height_min": 140 }, "bottom": { "inset_min": 32, "inset_ratio": 0.03, "offset_min": 12, "offset_ratio": 0.02, "container_margin_min": 40, "container_margin_ratio": 0.08, "text_inset_min": 18, "text_inset_ratio": 0.04, "overlay_height_min": 280 } } } ``` - `image_list` (or `images`) required, non-empty array. Each item must include `image_url` or `enhanced_url`. - `overlay` optional; supports `top_left`, `top_right`, `footer` (fallbacks: `title`, `image_text`, `image_category`, `image_duration`). - `columns` optional integer 1-6; defaults to 4. - `max_tiles` optional integer; defaults to 28. - `header_image_url` and `footer_image_url` optional. - `image_tiling_position` optional object for tile overlay layout and font overrides. Omit to use defaults. **image_tiling_position (optional)** Same schema and validation as `/image/enhance_and_generate_rollup_banner`. See details above. **Success response (200)** ```json { "status": "completed", "session_id": "rollup_123", "request_id": "rollup_123", "result_url": "https://cdn.samsar.one/rollups/rollup_123.png", "thumbnail_url": "https://cdn.samsar.one/rollups/rollup_123-thumb.png", "result_urls": ["https://cdn.samsar.one/rollups/rollup_123.png"], "creditsCharged": 30, "remainingCredits": 4970 } ``` CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/intro.md METADATA: title: SamsarOne API Docs description: Build generative video, image, chat, assistant, and narrative workflows with the SamsarOne API. image: img/og/samsar-docs-overview.png sidebar_position: 0 sidebar_label: Overview slug: / hide_table_of_contents: true PRIMARY_HEADING: Build generative media workflows with one API CONTENT_START
SamsarOne developer platform # Build generative media workflows with one API Create videos, transform images, generate speech and music, run inference, and publish results through **hosted Samsar.js**. Start with one Samsar API key, then explore supported deployment adapters when you need your own runtime.
Base URL https://api.samsar.one/v1 Bearer authentication
## Choose a workflow Each workflow starts with the hosted API. The [interactive model matrix](/models) connects model keys to API contracts and compatible adapters; [deployment adapters](/adapters) explains native and standalone options. ## Make your first request Install the client in your server application: ```bash npm install samsar-js ``` ```js title="Hosted Samsar.js: discover models, then create a video" const samsar = new SamsarClient({ apiKey: process.env.SAMSAR_API_KEY }); const models = await samsar.getSupportedTextToVideoModels(); console.log(models.data); // Check the available image and video selections. const job = await samsar.createVideoFromText({ prompt: 'A cinematic product reveal in a quiet gallery.', duration: 10, aspect_ratio: '16:9', image_model: 'GPTIMAGE2.5', video_model: 'RUNWAYML', }); // Persist the ID; generation continues asynchronously. console.log(job.data.request_id); ``` Follow the [status contract](/video#get-status) to poll to completion or configure a webhook. To use a local deployment, see [the adapter guide](/adapters#choose-your-path).
Prefer HTTP? Follow the equivalent request flow
1
Create an API key

Generate a key in Samsar Studio and keep it on your server. Never expose it in browser code.

2
Check express model availability

The video-generation allowlist can vary with the providers configured for a deployment.

```bash title="List express models available to this deployment" curl "https://api.samsar.one/v1/video/supported_models" ```
3
Submit work and track usage

Send your key as a bearer token. Asynchronous generation routes return a request ID; poll the documented status route or provide a webhook.

```bash title="Create a video" curl -X POST "https://api.samsar.one/v1/video/text_to_video" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "prompt": "A cinematic product reveal in a quiet gallery.", "duration": 10, "aspect_ratio": "16:9", "image_model": "GPTIMAGE2.5", "video_model": "RUNWAYML" } }' ``` :::tip Read credit headers When an endpoint deducts credits, inspect `x-credits-charged` and `x-credits-remaining`. See [Models & pricing](/pricing) for current rates and billing units. :::
## Pick the right API surface | Surface | Use it when… | Base path | | --- | --- | --- | | Core APIs | One Samsar account owns and submits the work. | `/v1/video`, `/v1/image`, `/v1/chat`, `/v1/assistant` | | V2 API | One client needs a unified surface for internal and external-user actors. | `/v2` | | External users | Your platform needs per-customer credits, history, and login state. | `/v1/external_users` | | External requests | A deployment uses Samsar as a managed fallback provider. | `/v2/external` | | Narrative API | You want story structure first and media rendering later. | `/v2/external/narrative` | ## Before you ship - Keep API keys in server-side secrets. - Store every asynchronous `request_id` before starting a polling loop. - Reuse stable client request IDs where an endpoint supports idempotent retries. - Query runtime model availability instead of hard-coding a deployment-wide assumption. - Treat credit headers as the final billed amount; estimates can change with duration and optional stages. CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/login-tokens.mdx METADATA: title: Login Tokens description: Mint short-lived login tokens and exchange them for client auth tokens in Samsar applications. image: img/og/login-tokens.png sidebar_position: 1 slug: /login-tokens sidebar_label: Login Tokens PRIMARY_HEADING: Login Tokens CONTENT_START # Login Tokens Use this endpoint to mint a short-lived `loginToken` from your API key. The login token can be exchanged for a long-lived client `authToken` via `/users/verify_token`. For external platform users such as Whop members, use [`POST /external_users/create_login_token`](/external-users#post-external_userscreate_login_token) instead. - Base URL: `https://api.samsar.one/v1` - Auth: `Authorization: Bearer ` ## POST /create_login_token Create a short-lived `loginToken` for a user associated with your API key. **Request body** No body is required. **Sample request** ```bash curl -X POST https://api.samsar.one/v1/create_login_token \ -H "Authorization: Bearer YOUR_API_KEY_****************" ``` **Success response (200)** ```json { "loginToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expiresInSeconds": 600, "expiresAt": "2026-02-02T12:00:00.000Z" } ``` **Notes** - The token is short-lived and intended for handoff to the client app. - `GET /create_login_token` is also supported and returns the same payload. ## Exchange login token for client auth token Once the client receives a `loginToken`, exchange it for a long-lived `authToken` using the user verification endpoint. **Sample request** ```bash curl "https://api.samsar.one/users/verify_token?loginToken=$LOGIN_TOKEN" ``` **Success response (200)** ```json { "authToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "_id": "...", "email": "user@example.com" } ``` For external-user login tokens, the response includes `isExternalUser: true` plus the external user profile fields and the external-user-scoped `authToken`. CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/models.mdx METADATA: title: Model matrix description: Explore every Samsar modality, compare model keys, and move from hosted Samsar.js to supported deployment adapters. slug: /models sidebar_label: Model matrix hide_table_of_contents: true PRIMARY_HEADING: One platform. Every model, in context. CONTENT_START # One platform. Every model, in context. Start with **hosted Samsar.js**, choose a model for your workflow, then explore the adapters available for your own deployment. Native inference and custom model endpoints have their own paths. The matrix keeps model keys, API contracts and deployment choices together. [Get an API key](https://app.samsar.one/account/apiKeys) · [SDK quick start](/#make-your-first-request) · [Models & pricing](/pricing) · [Adapter deployment guide](/adapters) ## Inference & vision Plan scenes, understand images, write copy and build assistants. API: https://docs.samsar.one/external-requests#chat | Model | Request key | Scope | Hosted Samsar | Adapters | Workflow | Notes | | --- | --- | --- | --- | --- | --- | --- | | GPT 6 Astra | gpt-6-astra (setup: gpt-5.6-sol) | deployment | Managed route | OpenAI, Samsar.js, GMICloud via GenBlaze, OpenRouter | Branching | Current public key. Older setup files may contain gpt-5.6-sol; the processor normalizes that alias. GenBlaze still requires an exact validated mapping. API: https://docs.samsar.one/external-requests#chat | | Claude Opus 5.5 | claude-opus-5.5 | deployment | Managed route | Anthropic, Samsar.js, OpenRouter | Branching | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#chat | | Gemini 3.1 Pro | gemini-3.1-pro | deployment | Managed route | Google Cloud, Samsar.js, GMICloud via GenBlaze, OpenRouter | Modality-specific | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#chat | | Kimi K3 | kimi-k3 (setup: KIMIK3) | deployment | Managed route | Kimi, Samsar.js | Modality-specific | Setup key KIMIK3; inference requests use kimi-k3. Native Kimi first, then Samsar; OpenRouter is not in this chain. API: https://docs.samsar.one/external-requests#chat | | Qwen 3.8 Max | QWEN3.8 | deployment | Managed route | Alibaba Cloud, Samsar.js, GMICloud via GenBlaze, OpenRouter | Modality-specific | Native Alibaba is a Docker option. Hosted Qwen uses OpenRouter internally; callers still use Samsar.js. API: https://docs.samsar.one/external-requests#chat | | GPT 6 Astra · extra high reasoning | gpt-6-astra-xhigh | option | Managed route | OpenAI, Samsar.js, OpenRouter | Branching | Reasoning option on GPT 6 Astra, not a separate provider model. Omitted from the normal runtime catalog. API: https://docs.samsar.one/external-requests#chat | ## Image generation Create scene images, illustrations and reusable visual assets. API: https://docs.samsar.one/image-api#post-imagetext_to_image | Model | Request key | Scope | Hosted Samsar | Adapters | Workflow | Notes | | --- | --- | --- | --- | --- | --- | --- | | Qwen Image 3.0 Pro | QWENIMAGE3PRO | deployment | Not advertised | Alibaba Cloud | Express | Standalone native adapter only. Requires Alibaba standard pay-as-you-go credentials; Token Plan credentials do not enable it. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | GPTImage 2.5 | GPTIMAGE2.5 | deployment | Managed route | Samsar.js, OpenAI, fal | Express, Branching | Versioned Samsar key for GPTImage 2.5. Legacy GenBlaze GPT Image 2 routes are excluded from this model. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Seedream 5 Pro | SEEDREAM | deployment | Managed route | Samsar.js, GMICloud via GenBlaze, fal | Express | Stable key currently maps to Seedream 5 Pro. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Nano Banana 2 | NANOBANANA2 | deployment | Managed route | Samsar.js, Google Cloud, GMICloud via GenBlaze, fal | Modality-specific | Image generation catalog model; not in the current Express image allowlist. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Nano Banana Pro | NANOBANANAPRO | deployment | Managed route | Samsar.js, Google Cloud, GMICloud via GenBlaze, fal | Express, Branching | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Wan 2.7 Pro | WAN2.7PRO | deployment | Managed route | Samsar.js, Alibaba Cloud, fal | Express | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | F-Lite | FLITE | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Google Imagen3 | IMAGEN3 | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Flux-1.1 Pro | FLUX1.1PRO | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Dall-E 3 | DALLE3 | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Flux-1 Pro | FLUX1PRO | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Flux 1.1 Ultra | FLUX1.1ULTRA | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Flux-1 Dev | FLUX1DEV | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Recraft V3 | RECRAFTV3 | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Stable Diffusion V3.5 | SDV3.5 | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Sana 4.5B | SANA4.5B | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Sana Sprint | SANASPRINT | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Recraft 20B | RECRAFT20B | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Lumalabs Photon | PHOTON | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Lumalabs Photon Flash | PHOTONFLASH | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Lumina V2 | LUMINAV2 | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Ideogram V3 | IDEOGRAMV3 | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | HiDream I1 | HIDREAMI1 | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/image-api#post-imagetext_to_image | | Custom image endpoint | CUSTOM_TEXT_TO_IMAGE: | custom | Not advertised | Custom endpoint | Modality-specific | Register a named standalone image adapter; use the exact returned key. Includes the optional Vast.ai FLUX.2 utility. API: https://docs.samsar.one/image-api#post-imagetext_to_image | ## Image editing Edit reference images, remove objects and fill selected regions. API: https://docs.samsar.one/v2#v2-image-edit-routes | Model | Request key | Scope | Hosted Samsar | Adapters | Workflow | Notes | | --- | --- | --- | --- | --- | --- | --- | | GPTImage 2.5 Edit | GPTIMAGE2.5EDIT | deployment | Managed route | Samsar.js, OpenAI | Modality-specific | Versioned Samsar key for GPTImage 2.5 editing. Legacy GenBlaze routes are excluded. API: https://docs.samsar.one/v2#v2-image-edit-routes | | Nano Banana 2 Edit | NANOBANANA2EDIT | deployment | Managed route | Samsar.js, Google Cloud, GMICloud via GenBlaze, fal | Modality-specific | Used by image enhancement and branding removal. Multi-output image-set jobs bypass GenBlaze. API: https://docs.samsar.one/v2#v2-image-edit-routes | | Nano Banana Pro Edit | NANOBANANAPROEDIT | deployment | Managed route | Samsar.js, Google Cloud, GMICloud via GenBlaze, fal | Modality-specific | Required by VidGenie image-list mode. Multi-output image-set jobs bypass GenBlaze. API: https://docs.samsar.one/v2#v2-image-edit-routes | | BRIA Eraser | BRIA_ERASER | deployment | Managed route | Samsar.js, GMICloud via GenBlaze, fal | Modality-specific | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/v2#v2-image-edit-routes | | BRIA GenFill | BRIA_GENFILL | deployment | Managed route | Samsar.js, GMICloud via GenBlaze, fal | Modality-specific | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/v2#v2-image-edit-routes | | Flux-1 Pro Fill | FLUX1PROFILL | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/v2#v2-image-edit-routes | | Flux-1.1 Pro Ultra Redux | FLUX1.1PROULTRAREDUX | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/v2#v2-image-edit-routes | | Flux-1.1 Pro Redux | FLUX1.1PROREDUX | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/v2#v2-image-edit-routes | ## Video generation Generate motion from text, an image or first and last frames. API: https://docs.samsar.one/external-requests#video | Model | Request key | Scope | Hosted Samsar | Adapters | Workflow | Notes | | --- | --- | --- | --- | --- | --- | --- | | RunwayML | RUNWAYML | deployment | Managed route | Samsar.js, Runway | Express | Express turns planned scene images into motion. This is different from a raw text-to-video model call. API: https://docs.samsar.one/external-requests#video | | Veo 3.1 Text to Video | VEO3.1 | deployment | Managed route | Samsar.js, Google Cloud, GMICloud via GenBlaze, fal | Modality-specific | Text-to-video model; the I2V variants have separate keys. API: https://docs.samsar.one/external-requests#video | | Veo 3.1 Fast Text to Video | VEO3.1FAST | deployment | Managed route | Samsar.js, Google Cloud, GMICloud via GenBlaze, fal | Modality-specific | Fast text-to-video model; use VEO3.1I2VFAST for Express scene animation. API: https://docs.samsar.one/external-requests#video | | Veo 3.1 Image to Video | VEO3.1I2V | deployment | Managed route | Samsar.js, Google Cloud, GMICloud via GenBlaze, fal | Express, Branching | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | | Veo 3.1 Fast Image to Video | VEO3.1I2VFAST | deployment | Managed route | Samsar.js, Google Cloud, GMICloud via GenBlaze, fal | Express, Branching | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | | Veo 3.1 First/Last Frame to Video | VEO3.1FLIV | deployment | Managed route | Samsar.js, GMICloud via GenBlaze, fal | Modality-specific | First/last-frame generation; not an Express video selection. API: https://docs.samsar.one/external-requests#video | | Cosmos 3 Super Image to Video | COSMOS3SUPERI2V | deployment | Managed route | Samsar.js, fal | Express, Branching | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | | Seedance 1.5 I2V | SEEDANCEI2V | deployment | Managed route | Samsar.js, GMICloud via GenBlaze, fal | Express | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | | Seedance 2.0 Image to Video | SEEDANCE2.0I2V | deployment | Not advertised | GMICloud via GenBlaze, fal | Express, Branching | Standalone provider-billed I2V. Exact GenBlaze route: seedance-2-0-260128. No Samsar adapter in the setup catalog. API: https://docs.samsar.one/external-requests#video | | Seedance 2.5 Image to Video | SEEDANCE2.5I2V | deployment | Managed route | Samsar.js, GMICloud via GenBlaze, fal | Express | 5, 10 or 15 second 720p scene buckets. Exact GenBlaze route: seedance-2-5-260628. Hosted worker uses GMICloud internally. API: https://docs.samsar.one/external-requests#video | | Kling 3 Pro Image to Video | KLINGIMGTOVID3PRO | deployment | Managed route | Samsar.js, GMICloud via GenBlaze, fal | Express | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | | Kling Turbo Image to Video | KLINGIMGTOVIDTURBO | deployment | Managed route | Samsar.js, GMICloud via GenBlaze, fal | Express | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | | Kling 1.6 Pro Image to Video | KLINGIMGTOVIDPRO | deployment | Managed route | Samsar.js, GMICloud via GenBlaze, fal | Modality-specific | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | | Kling 2.1 Master Image to Video | KLINGIMGTOVID2.1MASTER | deployment | Managed route | Samsar.js, GMICloud via GenBlaze, fal | Modality-specific | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | | Kling 2.1 Pro Image to Video | KLINGIMGTOVID2.1PRO | deployment | Managed route | Samsar.js, GMICloud via GenBlaze, fal | Modality-specific | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | | Kling 2.1 Standard Image to Video | KLINGIMGTOVID2.1STANDARD | deployment | Managed route | Samsar.js, GMICloud via GenBlaze, fal | Modality-specific | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | | Hailuo 02 Pro | HAILUOPRO | deployment | Managed route | Samsar.js, GMICloud via GenBlaze, fal | Modality-specific | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | | Happy Horse 1.1 I2V | HAPPYHORSEI2V | deployment | Managed route | Samsar.js, Alibaba Cloud, GMICloud via GenBlaze, fal | Express | Stable key currently maps to Happy Horse 1.1 I2V. API: https://docs.samsar.one/external-requests#video | | Kling 3 Pro Text2Vid | KLINGTXTTOVID3PRO | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/external-requests#video | | SD Video | SDVIDEO | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/external-requests#video | | Haiper 2.0 | HAIPER2.0 | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/external-requests#video | | Skyreels-i2v | SKYREELSI2V | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/external-requests#video | | Veo2 | VEO | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/external-requests#video | | Veo2 Img2Vid | VEOI2V | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/external-requests#video | | PixVerseV4.5 | PIXVERSEI2V | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/external-requests#video | | PixVerseV4.5 Fast | PIXVERSEI2VFAST | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/external-requests#video | | Pika2.2 I2V | PIKA2.2I2V | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/external-requests#video | | Magi Distilled | MAGIDISTILLED | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/external-requests#video | | Vidu Img2Vid | VIDUI2V | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/external-requests#video | | Custom video endpoint | CUSTOM_IMAGE_TO_VIDEO | custom | Not advertised | Custom endpoint | Modality-specific | Requires a compatible standalone image-to-video adapter and its configured authorization. API: https://docs.samsar.one/external-requests#video | ## Speech Turn dialogue into narration and voice tracks. API: https://docs.samsar.one/external-requests#audio | Model | Request key | Scope | Hosted Samsar | Adapters | Workflow | Notes | | --- | --- | --- | --- | --- | --- | --- | | OpenAI Text to Speech | OPENAI (setup: OPENAI_TTS) | deployment | Managed route | Samsar.js, OpenAI, GMICloud via GenBlaze | Modality-specific | Use OPENAI in the Express tts_model field. GenBlaze speech is conditional on an exact gpt-4o-mini-tts mapping. API: https://docs.samsar.one/external-requests#audio | | Google Text to Speech | GOOGLE (setup: GOOGLE_TTS) | deployment | Managed route | Samsar.js, Google Cloud | Modality-specific | Use GOOGLE in the Express tts_model field. API: https://docs.samsar.one/external-requests#audio | | ElevenLabs Speech | ELEVENLABS | deployment | Managed route | fal | Modality-specific | Hosted audio accepts ELEVENLABS. The standalone setup registry enables this speech model through fal only. API: https://docs.samsar.one/external-requests#audio | | PlayAI speech | PLAYAI | service | Managed route | Samsar.js | Modality-specific | Accepted by the hosted audio speech contract. Not advertised by the standalone setup matrix. API: https://docs.samsar.one/external-requests#audio | ## Music Generate a backing track for your video or an audio asset. API: https://docs.samsar.one/external-requests#audio | Model | Request key | Scope | Hosted Samsar | Adapters | Workflow | Notes | | --- | --- | --- | --- | --- | --- | --- | | Lyria 3 | LYRIA3 | deployment | Managed route | Samsar.js, Google Cloud, fal | Modality-specific | Google music credential validation can change the runtime order between Google and fal. API: https://docs.samsar.one/external-requests#audio | | ElevenLabs Music | ELEVENLABS_MUSIC | deployment | Managed route | Samsar.js, ElevenLabs, fal | Modality-specific | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#audio | | AudioCraft | AUDIOCRAFT | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/external-requests#audio | | Cassette AI | CASSETTEAI | studio | Not advertised | Check target Studio | Modality-specific | Listed in the Studio source catalog, outside the standalone setup matrix. Presence here does not promise a live hosted route, Express support or deployment availability. API: https://docs.samsar.one/external-requests#audio | | Custom music endpoint | CUSTOM_TEXT_TO_MUSIC | custom | Not advertised | Custom endpoint | Modality-specific | Requires a compatible configured text-to-music endpoint. API: https://docs.samsar.one/external-requests#audio | ## Lip sync Match a visible speaker to a supplied audio track. API: https://docs.samsar.one/external-requests#video | Model | Request key | Scope | Hosted Samsar | Adapters | Workflow | Notes | | --- | --- | --- | --- | --- | --- | --- | | Sync Lip Sync | SYNCLIPSYNC | deployment | Managed route | Samsar.js, fal | Modality-specific | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | | LatentSync | LATENTSYNC | deployment | Managed route | Samsar.js, fal | Modality-specific | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | | Kling Lip Sync | KLINGLIPSYNC | deployment | Managed route | Samsar.js, fal | Modality-specific | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | | Hummingbird Lip Sync | HUMMINGBIRDLIPSYNC | deployment | Managed route | Samsar.js, fal | Modality-specific | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | | Creatify Lip Sync | CREATIFYLIPSYNC | deployment | Managed route | Samsar.js, fal | Modality-specific | Enable a supported adapter and check the target deployment before selecting this model. API: https://docs.samsar.one/external-requests#video | ## Sound effects Add sound to an existing video or generate an audio effect. API: https://docs.samsar.one/external-requests#video | Model | Request key | Scope | Hosted Samsar | Adapters | Workflow | Notes | | --- | --- | --- | --- | --- | --- | --- | | MMAudio V2 | MMAUDIOV2 | deployment | Managed route | Samsar.js, fal | Modality-specific | Video-conditioned sound. This is separate from the audio-only SDAUDIO route. API: https://docs.samsar.one/external-requests#video | | Mirelo AI | MIRELOAI | deployment | Managed route | Samsar.js, fal | Modality-specific | Video-conditioned sound. Provide a video URL and the desired sound prompt. API: https://docs.samsar.one/external-requests#video | | Audio sound effects | SDAUDIO | service | Managed route | Samsar.js | Modality-specific | Hosted text-to-sound-effect audio route. Different from video-conditioned MMAudio and Mirelo. API: https://docs.samsar.one/external-requests#audio | ## Embeddings & search Index content for semantic search and recommendations. API: https://docs.samsar.one/chat-api#post-chatcreate_embedding | Model | Request key | Scope | Hosted Samsar | Adapters | Workflow | Notes | | --- | --- | --- | --- | --- | --- | --- | | OpenAI embeddings | text-embedding-3-large | service | Managed route | Samsar.js, OpenAI | Modality-specific | Hosted search is accessed through Samsar.js. Local indexing still requires OPENAI_API_KEY; crawling URLs also requires FIRECRAWL_API_KEY. API: https://docs.samsar.one/chat-api#post-chatcreate_embedding | ## Transcription Align speech with text for transcripts and subtitles. API: https://docs.samsar.one/external-requests#audio | Model | Request key | Scope | Hosted Samsar | Adapters | Workflow | Notes | | --- | --- | --- | --- | --- | --- | --- | | Whisper transcript alignment | whisper-1 | service | Managed route | Samsar.js, OpenAI | Modality-specific | Managed transcript-alignment route; not a freely selectable Express inference model. API: https://docs.samsar.one/external-requests#audio | ## How to read the matrix - **Samsar.js** is the client for Samsar APIs. With the default hosted URL, your request is handled by Samsar; you do not need to configure each upstream provider. - **Deployment adapters** list compatible integrations from the setup registry. Samsar.js leads media documentation; native inference adapters may appear first. This display order does not change fallback behavior. - **Express** marks models in the source allowlists for text-to-video and image-list-to-video. **Branching** marks the narrower narrative allowlists. Check the deployment response before submitting. - **Studio catalog** entries appear in the source catalog but are outside the standalone setup matrix. They are not a promise of a live hosted route. - **Custom endpoint** entries require local configuration. **Managed service** entries describe operation-specific APIs such as search, speech and transcript alignment. The Express `tts_model` values are `OPENAI`, `GOOGLE` and `ELEVENLABS`; setup labels such as `OPENAI_TTS` are not interchangeable with every endpoint field. Similarly, `KIMIK3` is the setup key and `kimi-k3` is the inference request value. Use the linked API contract for the exact payload. ## Check the target deployment ```js title="Inspect hosted Express model availability with samsar-js" const samsar = new SamsarClient({ apiKey: process.env.SAMSAR_API_KEY }); const available = await samsar.getSupportedTextToVideoModels(); console.log(available.data); ``` This endpoint covers Express workflows. It is not an inventory of every Studio or audio model. In a standalone deployment, also inspect `runtime/config/available-models.json` and **Settings → Model Adapters**. GenBlaze exposes only the exact routes validated for your GMICloud credential. The matrix is generated from the Samsar setup registry, processor catalogs and workflow allowlists. It is a documented capability snapshot, not a live provider status page. See the [source matrix and maintenance instructions](https://github.com/samsarone/samsar/blob/main/pages/model-matrix.md). CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/narrative-api/create-branching.mdx METADATA: title: Create a branching narrative description: Expand a completed singular narrative into a validated binary tree of alternate movie-resource paths. image: img/og/samsar-docs-overview.png slug: /narrative/create-branching sidebar_label: Create branching PRIMARY_HEADING: Create a branching narrative CONTENT_START # Create a branching narrative `POST /v2/external/narrative/create_branching` Expand a completed [`create_single`](/narrative/create-single) request into a binary tree of alternate narrative paths. The source narrative remains unchanged. Samsar creates a separate `NarrativeRequest` with `narrative_type: "branched"` and returns HTTP `202` as soon as the new request has been persisted. This endpoint performs narrative inference only. It does not create a `VideoSession` or generate image, speech, audio, or video media. After it completes, pass its request ID to [Render a narrative as video](/narrative/render-video) when you want one final video per leaf path. ## Request body ```json { "narrative_request_id": "687b3df76a8d56c94ab32100", "num_levels": 1 } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `narrative_request_id` | string | Yes | ID of the completed singular `NarrativeRequest` to branch. It must belong to the authenticated user. | | `num_levels` | integer | Yes | Number of binary branching levels to generate. The standard maximum is `3`, and it cannot exceed the source scene count minus one. | The new request inherits the source request's prompt, duration, inference model, and speaker configuration. Samsar also stores an immutable snapshot of the source prompt, duration, model, theme, narrative, and movie resource list for branching and recovery. Do not send a new `prompt` or `inference_model` in this request. `narrativeRequestId`, `request_id`, `requestId`, and `id` are accepted as compatibility aliases for `narrative_request_id`. `numLevels` is accepted as an alias for `num_levels`. The fields may also be placed inside a top-level `input` object, but new integrations should use the flat body above. ## Source requirements The source must: - belong to the authenticated user; - have `request_type: "create_single"` and `narrative_type: "singular"`; - have reached `COMPLETED` with a successful generation outcome; - contain valid `themeJson`, `narrativeJson`, and `movieResourceList` artifacts; - contain at least two scenes and a sounds list; and - contain enough scenes to place every requested branching level at a distinct index. The request is rejected before a new item is created if these requirements are not met. A positive credit balance is also required at submission and when queued inference begins. ## Submit a request ```bash curl -X POST "https://api.samsar.one/v2/external/narrative/create_branching" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "narrative_request_id": "687b3df76a8d56c94ab32100", "num_levels": 1 }' ``` ## samsar-js The polling helper submits the branching request and resolves with the completed typed tree: ```ts const samsar = new SamsarClient({ apiKey: process.env.SAMSAR_API_KEY!, }); const result = await samsar.createV2ExternalBranchingNarrativeAndPoll( { narrative_request_id: '687b3df76a8d56c94ab32100', num_levels: 1, }, { pollIntervalMs: 2_000, pollTimeoutMs: 10 * 60 * 1_000, }, ); const { movieResourceList, branchingMeta, billing } = result.data; ``` To persist the new request ID before polling, use the lower-level methods: ```ts const queued = await samsar.createV2ExternalBranchingNarrative({ narrative_request_id: '687b3df76a8d56c94ab32100', num_levels: 1, }); const completed = await samsar.pollV2ExternalNarrative( queued.data.request_id, { pollIntervalMs: 2_000 }, ); ``` The generic narrative status and polling helpers work with both singular and branched requests. Polling helpers throw `SamsarRequestError` if a request reaches `FAILED`, polling is aborted, or the timeout is reached. ## Accepted response HTTP `202`: ```json { "request_id": "687b4127b503e2b4acdf9876", "requestId": "687b4127b503e2b4acdf9876", "request_type": "create_branching", "narrative_type": "branched", "status": "PENDING", "poll_url": "/v2/external/narrative/status?request_id=687b4127b503e2b4acdf9876", "prompt": "A grounded short film about a night market waking before dawn.", "duration": 45, "inference_model": "gemini-3.1-pro", "source_narrative_request_id": "687b3df76a8d56c94ab32100", "num_levels": 1, "created_at": "2026-07-19T10:00:00.000Z", "updated_at": "2026-07-19T10:00:00.000Z" } ``` Persist the new `request_id`, not the source ID. `poll_url` is a relative canonical path. ## Poll the request ```bash curl "https://api.samsar.one/v2/external/narrative/status?request_id=687b4127b503e2b4acdf9876" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` The canonical status route accepts `request_id`, `requestId`, or `id` as the query parameter. Branch-specific aliases are also available: - `GET /v2/external/narrative/create_branching/status?request_id=...` - `GET /v2/external/narrative/{request_id}/status` - `GET /v2/external/narrative/create_branching/{request_id}/status` Polling returns HTTP `200` for every stored state: | Status | Meaning | | --- | --- | | `PENDING` | The request is persisted and waiting for the user's narrative-processing slot. | | `PROCESSING` | Divergence planning, branch generation, validation, or usage settlement is in progress. | | `COMPLETED` | Terminal success. The complete tree and branching metadata are available. | | `FAILED` | Terminal failure. Inspect `error` and `billing`; provider work already completed can remain billable. | Requests can recover from processor restarts using stored checkpoints. Continue polling the same new request ID while its state is non-terminal. ## Tree structure The completed `movieResourceList` is a binary tree rather than the linear `{ scenes, sounds }` object returned by `create_single`: ```json { "structureType": "branched", "schemaVersion": 1, "rootNodeId": "root", "numLevels": 1, "branchingFactor": 2, "branchSceneIndices": [1], "nodes": [ { "nodeId": "root", "parentNodeId": null, "childNodeIds": ["root.1", "root.2"], "level": 0, "childIndex": null, "branchOrdinal": null, "divergence": null, "scenes": ["...complete source scenes..."], "sounds": ["...complete source sounds..."] }, { "nodeId": "root.1", "parentNodeId": "root", "childNodeIds": [], "level": 1, "childIndex": 0, "branchOrdinal": 1, "divergence": { "divergenceSceneIndex": 1, "path_name": "Riverside Awakening", "path_description": "The story follows the community gathering at the river." }, "scenes": ["...complete branch scenes..."], "sounds": ["...complete branch sounds..."] }, { "nodeId": "root.2", "parentNodeId": "root", "childNodeIds": [], "level": 1, "childIndex": 1, "branchOrdinal": 2, "divergence": { "divergenceSceneIndex": 1, "path_name": "Soi Sunrise Ritual", "path_description": "The story remains in the neighborhood as its morning rituals unfold." }, "scenes": ["...complete branch scenes..."], "sounds": ["...complete branch sounds..."] } ], "branchPoints": [ { "branchPointId": "branch-point:root", "parentNodeId": "root", "level": 1, "divergenceSceneIndex": 1, "status": "COMPLETED", "divergencePaths": [ { "childNodeId": "root.1", "path_name": "Riverside Awakening", "path_description": "The story follows the community gathering at the river." }, { "childNodeId": "root.2", "path_name": "Soi Sunrise Ritual", "path_description": "The story remains in the neighborhood as its morning rituals unfold." } ] } ] } ``` ### Reading the tree - `root` is the source `movieResourceList`. Its first children are `root.1` and `root.2`; deeper IDs extend the path, such as `root.1.1` and `root.1.2`. - `branchSceneIndices` contains zero-based scene indexes. A value of `1` means the child preserves scenes and sounds through scene index `1`, then diverges after it. - Branch points are distributed at approximately equal intervals. For a nine-scene source with `num_levels: 2`, the indexes are `[2, 5]`, meaning branches occur after scene numbers 3 and 6. - `childIndex` is zero-based within its parent. `branchOrdinal` and the numeric segment in `nodeId` are one-based. - Every node contains a complete `scenes` list and a complete `sounds` list for that path. Consumers do not need to reconstruct a child by joining it to its parent. - Every child preserves its parent's scenes and sounds through the divergence index, including that indexed scene. The remaining suffix follows `path_description` and retains the source actors and timeline. - Each non-leaf node has exactly two complementary children. A tree with `n` levels contains `2^(n + 1) - 1` complete nodes. `branchPoints` records the two path choices generated for every expanded parent. The completed response also returns `branchingMeta`, a compact projection containing `rootNodeId`, `branchSceneIndices`, `branchPoints`, `leafNodeIds`, and `nodeCount`. ## Completed response ```json { "request_id": "687b4127b503e2b4acdf9876", "request_type": "create_branching", "narrative_type": "branched", "status": "COMPLETED", "source_narrative_request_id": "687b3df76a8d56c94ab32100", "num_levels": 1, "themeJson": { "...": "copied source theme fields" }, "narrativeJson": { "...": "copied source narrative fields" }, "movieResourceList": { "structureType": "branched", "rootNodeId": "root", "nodes": ["...complete tree nodes..."], "branchPoints": ["...completed branch points..."] }, "branchingMeta": { "schemaVersion": 1, "numLevels": 1, "branchingFactor": 2, "rootNodeId": "root", "branchSceneIndices": [1], "branchPoints": ["...completed branch points..."], "leafNodeIds": ["root.1", "root.2"], "nodeCount": 3 }, "billing": { "pricing_multiplier": 1.5, "underlying_cost_usd": 0.0226304, "underlying_credits": 2.263, "credits_charged": 3.3946, "remaining_credits": 996.6054, "usage": { "inputTokens": 16252, "outputTokens": 10081, "cachedInputTokens": 0, "reasoningTokens": 8668 } }, "creditsCharged": 3.3946, "remainingCredits": 996.6054, "completed_at": "2026-07-19T10:00:28.000Z" } ``` The completed branched request keeps the source `themeJson` and `narrativeJson`. Its `movieResourceList` is the new validated tree. The tree remains stored on `NarrativeRequest`; the narrative-to-video route validates and converts it into a branch-aware `VideoSession` render plan. ## Billing The endpoint is metered at **1.5x the cumulative underlying inference cost**: ```text credits_charged = sum(all underlying provider/model inference costs in USD) × 100 credits/USD × 1.5 ``` Metered work includes: - the divergence-planning call for every expanded parent node; - both child-branch generation calls for every expanded parent node; - every additional provider call made after structural or semantic validation fails; and - all inference retries that contribute to a completed or failed request. Usage is accumulated across all providers and models involved in the request before the multiplier is applied once. The charge is therefore based on actual input, output, cached-input, and reasoning-token usage at each provider's underlying model rate—not only the final successful response. Provider calls completed before a terminal failure remain billable. If complete billable usage cannot be obtained for every inference response, the request fails closed instead of undercharging. Once inference begins, its exact incurred cost is settled in full, so `remaining_credits` can become negative when actual usage exceeds the balance available at submission. When available, polling responses include the `billing` object plus `x-credits-charged` and `x-credits-remaining` response headers. ## Failure and HTTP semantics Submission errors are returned synchronously and do not create a branching request: - `400` missing or invalid `narrative_request_id` or `num_levels`; the requested levels exceed the source's available scene indexes. - `401` invalid or expired credentials. - `402` insufficient credits or an API-key usage limit was reached. - `403` the supplied credential type is not supported. - `404` the authenticated user or owned source narrative was not found. - `409` the source narrative has not completed. - `422` the source is not singular and successful, or its stored artifacts are invalid. - `503` narrative persistence or billing indexes are temporarily unavailable. Inference, validation, usage, and settlement failures that happen after HTTP `202` are stored on the new request. Polling still returns HTTP `200`, with `status: "FAILED"` and the original status and machine-readable code inside `error`: ```json { "request_id": "687b4127b503e2b4acdf9876", "request_type": "create_branching", "narrative_type": "branched", "status": "FAILED", "source_narrative_request_id": "687b3df76a8d56c94ab32100", "num_levels": 1, "error": { "message": "Branching narrative generation failed.", "code": "BRANCHING_TREE_VALIDATION_FAILED", "status": 502 }, "billing": { "pricing_multiplier": 1.5, "underlying_cost_usd": 0.008, "underlying_credits": 0.8, "credits_charged": 1.2, "remaining_credits": 998.8, "usage": { "inputTokens": 2400, "outputTokens": 300, "cachedInputTokens": 0, "reasoningTokens": 100 } } } ``` ## What this endpoint does not do `create_branching` stops after the narrative tree and its metadata have been generated, validated, stored, and metered. It does not: - modify the source singular `NarrativeRequest`; - create or update a `VideoSession`; - generate or edit images; - generate speech, sound effects, music, or other audio files; or - generate, assemble, or publish video. CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/narrative-api/create-single.mdx METADATA: title: Create a single narrative description: Submit and poll an asynchronous request that creates validated theme, narrative, and movie-resource JSON from one prompt. image: img/og/samsar-docs-overview.png slug: /narrative/create-single sidebar_label: Create single PRIMARY_HEADING: Create a single narrative CONTENT_START # Create a single narrative `POST /v2/external/narrative/create_single` Generate the structured output of the text-to-video prompt-generation stage without creating a `VideoSession` or starting media generation. The request runs asynchronously and returns HTTP `202` as soon as it has been persisted. ## Request body ```json { "prompt": "A grounded short film about a night market waking before dawn.", "duration": 45, "inference_model": "GPT6" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `prompt` | string | Yes | Narrative direction. Leading and trailing whitespace is removed. Maximum length: 4,000 characters. | | `duration` | number | Yes | Target narrative duration in seconds, from `10` through `240`. This guides narrative structure; it does not render media. | | `inference_model` | string | No | Inference model for the prompt-generation workflow. Omit it to use the authenticated user's selected inference model. | The canonical model names and concise aliases are: | Request value | Stored model | | --- | --- | | `GPT6` or `gpt-6-astra` | `gpt-6-astra` | | `GEMINI3.1` or `gemini-3.1-pro` | `gemini-3.1-pro` | | `QWEN3.8` | `QWEN3.8` | `QWEN3.8` selects Qwen 3.8 Max for both inference and vision input. `inferenceModel` is accepted as a compatibility alias for `inference_model`. The request fields may also be placed inside a top-level `input` object, but new integrations should use the flat body shown above. ## Submit a request ```bash curl -X POST "https://api.samsar.one/v2/external/narrative/create_single" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "A grounded short film about a night market waking before dawn.", "duration": 45, "inference_model": "GEMINI3.1" }' ``` ## samsar-js The polling helper submits the request and resolves with the completed, typed artifact response: ```ts const samsar = new SamsarClient({ apiKey: process.env.SAMSAR_API_KEY!, }); const result = await samsar.createV2ExternalSingleNarrativeAndPoll( { prompt: 'A grounded short film about a night market waking before dawn.', duration: 45, inference_model: 'GEMINI3.1', }, { pollIntervalMs: 2_000, pollTimeoutMs: 10 * 60 * 1_000, }, ); const { themeJson, narrativeJson, movieResourceList, billing } = result.data; ``` To persist the request ID before polling, use the lower-level methods: ```ts const queued = await samsar.createV2ExternalSingleNarrative({ prompt: 'A grounded short film about a night market waking before dawn.', duration: 45, inference_model: 'GEMINI3.1', }); const completed = await samsar.pollV2ExternalNarrative( queued.data.request_id, { pollIntervalMs: 2_000 }, ); ``` The polling helpers throw `SamsarRequestError` when the request reaches `FAILED`, polling is aborted, or the polling timeout is reached. ## Accepted response HTTP `202`: ```json { "request_id": "687b3df76a8d56c94ab32100", "requestId": "687b3df76a8d56c94ab32100", "status": "PENDING", "poll_url": "/v2/external/narrative/status?request_id=687b3df76a8d56c94ab32100", "prompt": "A grounded short film about a night market waking before dawn.", "duration": 45, "inference_model": "gemini-3.1-pro", "created_at": "2026-07-18T10:00:00.000Z", "updated_at": "2026-07-18T10:00:00.000Z" } ``` Persist `request_id` immediately. `poll_url` is a relative canonical path. ## Poll the request ```bash curl "https://api.samsar.one/v2/external/narrative/status?request_id=687b3df76a8d56c94ab32100" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` The canonical status route accepts `request_id`, `requestId`, or `id` as the query parameter. These path-based aliases are also available: - `GET /v2/external/narrative/create_single/status?request_id=...` - `GET /v2/external/narrative/{request_id}/status` - `GET /v2/external/narrative/create_single/{request_id}/status` Polling returns HTTP `200` for every stored request state: | Status | Meaning | | --- | --- | | `PENDING` | The request is persisted and waiting for a worker. Only one narrative request runs per user, so another accepted request can remain pending until the active request finishes. | | `PROCESSING` | Prompt generation, validation, or usage settlement is in progress. | | `COMPLETED` | Terminal success. The three generated artifacts are present. | | `FAILED` | Terminal failure. Read `error` and `billing` for details. | Requests can recover from a processor restart, so continue polling a non-terminal request using the same `request_id`. ## Completed response The generated objects below are abbreviated; their contents depend on the prompt, duration, and selected model. ```json { "request_id": "687b3df76a8d56c94ab32100", "requestId": "687b3df76a8d56c94ab32100", "status": "COMPLETED", "poll_url": "/v2/external/narrative/status?request_id=687b3df76a8d56c94ab32100", "prompt": "A grounded short film about a night market waking before dawn.", "duration": 45, "inference_model": "gemini-3.1-pro", "themeJson": { "...": "generated theme fields" }, "movieResourceList": { "...": "final enriched movie resource fields" }, "narrativeJson": { "...": "validated narrative fields" }, "billing": { "pricing_multiplier": 1.5, "underlying_cost_usd": 0.042, "underlying_credits": 4.2, "credits_charged": 6.3, "remaining_credits": 993.7, "usage": { "inputTokens": 12000, "outputTokens": 3500, "cachedInputTokens": 0, "reasoningTokens": 900 } }, "creditsCharged": 6.3, "remainingCredits": 993.7, "completed_at": "2026-07-18T10:00:24.000Z", "created_at": "2026-07-18T10:00:00.000Z", "updated_at": "2026-07-18T10:00:24.000Z" } ``` - `themeJson` is the generated theme used by the narrative workflow. - `narrativeJson` is the validated narrative before final enrichment. - `movieResourceList` is the final enriched structure and is compatible with the corresponding `VideoSession.movieResourceList` field. - `movieResourceList` is returned for your application to store or process; this endpoint does not attach it to a `VideoSession`. ## Billing The charge is calculated from all inference calls that contributed to the request, including retries and validation or repair calls: ```text credits_charged = sum(underlying inference cost in USD) × 100 credits/USD × 1.5 ``` The multiplier is applied once after the underlying costs have been combined across providers. The exact charge therefore depends on the selected model, prompt and output size, and the number of inference or retry calls required. When billing is available, the polling response also includes: - `x-credits-charged` - `x-credits-remaining` Provider calls completed before a terminal failure remain billable. Only one narrative request is processed at a time for each user. The service checks for a positive credit balance both when a request is submitted and immediately before its inference begins, so a queued request can fail with `error.status: 402` if its user no longer has positive credits when it reaches the front of the queue. Once inference begins, the accepted request's exact incurred usage is always settled in full. The final `remaining_credits` value can therefore be negative when actual usage exceeds the balance available at submission time. Add credits before submitting further narrative work. ## Failed response A failed request is returned by the polling endpoint with HTTP `200` and `status: "FAILED"`: ```json { "request_id": "687b3df76a8d56c94ab32100", "status": "FAILED", "prompt": "A grounded short film about a night market waking before dawn.", "duration": 45, "inference_model": "gemini-3.1-pro", "error": { "message": "Narrative generation failed.", "code": "NARRATIVE_GENERATION_FAILED", "status": 500 }, "billing": { "pricing_multiplier": 1.5, "underlying_cost_usd": 0.008, "underlying_credits": 0.8, "credits_charged": 1.2, "remaining_credits": 998.8, "usage": { "inputTokens": 2400, "outputTokens": 300, "cachedInputTokens": 0, "reasoningTokens": 100 } }, "creditsCharged": 1.2, "remainingCredits": 998.8, "failed_at": "2026-07-18T10:00:12.000Z" } ``` ## Common HTTP errors - `400` missing or invalid `prompt`, `duration`, `inference_model`, or `request_id`; missing authentication headers. - `401` invalid or expired credentials. - `402` insufficient credits or an API-key usage limit was reached. - `403` the supplied credential type is not supported by this route. - `404` the authenticated user or narrative request was not found. - `503` narrative request persistence or billing indexes are temporarily unavailable. Content moderation, inference, and final validation failures are stored on the asynchronous request. Polling exposes their original status and machine-readable code inside `error`. ## What this endpoint does not do `create_single` stops after Samsar has built and validated the final narrative resource structure. It does not: - create or update a `VideoSession`; - generate or edit images; - generate speech, sound effects, music, or other audio files; - generate, assemble, or publish video. The requested `duration` controls narrative planning only. CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/narrative-api/index.mdx METADATA: title: Narrative API description: Generate reusable, validated narrative structures without creating a video session or rendering media. image: img/og/samsar-docs-overview.png slug: /narrative sidebar_label: Overview PRIMARY_HEADING: Narrative API CONTENT_START # Narrative API The Narrative API creates reusable narrative structures without starting Samsar's image, audio, or video-generation stages. Generate a singular narrative from a prompt, then optionally expand that completed narrative into a binary tree of alternate paths. **Quick facts** - Base URL: `https://api.samsar.one` - Route prefix: `/v2/external/narrative` - Authentication: `Bearer Samsar API key` - Execution: `Asynchronous · HTTP 202 and request polling` - Billing: `Actual inference usage at 1.5×` - Content type: `application/json` Narrative processing is serialized to one active request per user; additional accepted requests remain pending. Use this API when you need the same validated narrative structure that a text-to-video request would build during prompt generation, but want to decide separately how or whether to render it. ## Workflow 1. Submit a narrative request and store the returned `request_id`. 2. Poll the request until its status is `COMPLETED` or `FAILED`. 3. On success, read `themeJson`, `narrativeJson`, and `movieResourceList` from the status response. 4. Optionally submit the completed singular request ID to `create_branching` and poll the new request ID for a branched resource tree. 5. When ready to render, submit either completed request ID to `POST /v2/external/video/narrative_to_video`. Status reads are scoped to the authenticated user. Keep the same credentials for submission and polling. If the user submits another narrative while one is active, the additional accepted request remains `PENDING` until the active request reaches a terminal state. ## Routes | Method | Route | Purpose | | --- | --- | --- | | `POST` | `/v2/external/narrative/create_single` | Generate one validated narrative resource list from a prompt. | | `POST` | `/v2/external/narrative/create_branching` | Expand a completed singular request into a validated binary narrative tree. | | `GET` | `/v2/external/narrative/status?request_id=...` | Poll a narrative request. | | `POST` | `/v2/external/video/narrative_to_video` | Render a completed singular or branched narrative without rerunning prompt generation. | See [`create_single`](/narrative/create-single) to generate a singular resource list, [`create_branching`](/narrative/create-branching) to generate alternate paths, and [Render a narrative as video](/narrative/render-video) to start the media pipeline from either result. ## Returned artifacts | Field | Description | | --- | --- | | `themeJson` | The generated theme data used to guide the narrative. | | `narrativeJson` | The validated narrative before final movie-resource enrichment. | | `movieResourceList` | A linear stage-one structure for a singular request, or a complete node-based tree for a branched request. | | `branchingMeta` | Branched requests only: compact branch-point, leaf, and node metadata for the generated tree. | These artifacts are stored on a standalone narrative request. Narrative creation does **not** create or modify a `VideoSession`, and it does not generate images, speech or audio files, music, or video. Both singular and branched results can later be rendered explicitly through `POST /v2/external/video/narrative_to_video`. ## Authentication A Samsar API key is recommended: ```bash -H "Authorization: Bearer $SAMSAR_API_KEY" ``` The API also accepts a signed Samsar user auth token. Deployments using app credentials may authenticate with `Authorization: AppKey ` and `x-app-secret: `. Missing, expired, or invalid credentials are rejected before a request is created. ## Metering Narrative requests are usage-based. Samsar prices every inference response used to produce the terminal result at its underlying provider and model rate, sums those costs, and applies the **1.5x narrative multiplier once** to the total. For `create_single`, metered work includes theme generation, narrative generation, semantic validation or repair, retries, moderation inference when billable usage is returned, and any other inference call needed to produce the final linear `movieResourceList`. For `create_branching`, it includes every divergence-planning call, child-branch generation call, and validation retry across the entire tree. Calls completed before a failed request can still be charged because that provider usage was incurred. A positive credit balance is required before new narrative inference begins. Once a request is accepted and inference has started, its exact incurred usage is settled in full. If the final charge is greater than the balance available at submission time, `remaining_credits` can be negative; add credits before submitting more narrative work. Terminal status responses include a `billing` object. They also include `x-credits-charged` and `x-credits-remaining` response headers when those values are available. CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/narrative-api/render-video.mdx METADATA: title: Render a narrative as video description: Start the express media pipeline from a completed singular or branching NarrativeRequest. image: img/og/samsar-docs-overview.png slug: /narrative/render-video sidebar_label: Render as video PRIMARY_HEADING: Render a narrative as video CONTENT_START # Render a narrative as video `POST /v2/external/video/narrative_to_video` Start an express video from a completed `NarrativeRequest`. Samsar copies the stored `themeJson`, `narrativeJson`, and `movieResourceList` into a new `VideoSession`, skips prompt generation, and continues with image, speech, music, AI-video, frame, and final-video generation. The source can be `narrative_type: "singular"` or `narrative_type: "branched"`. The source request is never modified. ## Request body ```json { "input": { "narrative_request_id": "687b4127b503e2b4acdf9876", "image_model": "GPTIMAGE2.5", "video_model": "RUNWAYML" }, "webhookUrl": "https://example.com/webhooks/video" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `narrative_request_id` | string | Yes | Owned, completed singular or branched `NarrativeRequest` ID. | | `image_model` | string | No | Express image-model override. The authenticated user's configured default is used when omitted. | | `video_model` | string | No | Express video-model override. The authenticated user's configured default is used when omitted. | | `webhookUrl` | string | No | Top-level URL that receives one terminal session webhook. | The ID aliases `narrativeRequestId`, `session_id`, `sessionId`, `request_id`, and `requestId` are accepted. Model aliases may be camel-cased. Do not provide `prompt` or `duration`; both are inherited from the source request and overrides are rejected. ## Submit and poll ```bash curl -X POST "https://api.samsar.one/v2/external/video/narrative_to_video" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "narrative_request_id": "687b4127b503e2b4acdf9876" } }' ``` HTTP `200` returns the new video request ID: ```json { "request_id": "687b4d244d7798b1fbec0123", "session_id": "687b4d244d7798b1fbec0123", "source_narrative_request_id": "687b4127b503e2b4acdf9876", "status": "PENDING" } ``` Poll the video route, not the narrative status route: ```bash curl "https://api.samsar.one/v2/external/video/status?request_id=687b4d244d7798b1fbec0123" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` ## Branch rendering For a singular source, the session follows the existing linear pipeline and returns one video. For a branched source, each leaf node is a complete linear viewing path. Samsar generates shared image, speech, music, and scene-video assets once, then generates path-specific frames and one final video for every leaf. A tree with `num_levels: 2` therefore returns four videos. ### Compact branch status The normal status response includes a normalized `branching` manifest with schema `branched_video_status.v1`. It is compact enough for regular polling: it contains tree choices, path-level frame and video status, aggregate progress, and completed path URLs, but it does not contain rendered frame lists or duplicate the shared media assets. The example below shows one path waiting for final video generation while its sibling is still generating frames: ```json { "request_id": "687b4d244d7798b1fbec0123", "session_id": "687b4d244d7798b1fbec0123", "status": "PENDING", "narrative_type": "branched", "render_plan_version": 1, "default_path_id": "root.1", "branching": { "schema": "branched_video_status.v1", "status": "PENDING", "is_complete": false, "finalized": false, "render_plan_version": 1, "default_path_id": "root.1", "summary": { "total_paths": 2, "completed_paths": 0, "pending_paths": 2, "failed_paths": 0, "cancelled_paths": 0, "frame_paths_completed": 1, "video_paths_completed": 0, "progress_percent": 25 }, "tree": { "root_node_id": "root", "num_levels": 1, "branching_factor": 2, "node_count": 3, "leaf_node_ids": ["root.1", "root.2"], "branch_scene_indices": [2], "choice_points": [ { "branch_point_id": "branch-point:root", "parent_node_id": "root", "level": 1, "divergence_scene_index": 2, "switch_at_seconds": 15, "options": [ { "child_node_id": "root.1", "branch_ordinal": 1, "path_name": "Follow the signal", "path_description": "Trace the mysterious broadcast to its source.", "leaf_path_ids": ["root.1"] }, { "child_node_id": "root.2", "branch_ordinal": 2, "path_name": "Shut it down", "path_description": "Destroy the receiver before the signal spreads.", "leaf_path_ids": ["root.2"] } ] } ] }, "paths": [ { "path_id": "root.1", "leaf_node_id": "root.1", "ordinal": 0, "is_default": true, "node_ids": ["root", "root.1"], "duration": 60, "status": "PENDING", "current_stage": "video_generation", "stages": { "frame_generation": "COMPLETED", "video_generation": "PENDING" }, "stage_details": { "frame_generation": { "status": "COMPLETED", "pending": false }, "video_generation": { "status": "PENDING", "pending": true } }, "selection_trail": [ { "branch_point_id": "branch-point:root", "node_id": "root.1", "parent_node_id": "root", "level": 1, "divergence_scene_index": 2, "switch_at_seconds": 15, "path_name": "Follow the signal" } ] }, { "path_id": "root.2", "leaf_node_id": "root.2", "ordinal": 1, "is_default": false, "node_ids": ["root", "root.2"], "duration": 60, "status": "PENDING", "current_stage": "frame_generation", "stages": { "frame_generation": "PENDING", "video_generation": "INIT" } } ], "outputs": { "ready": false, "default_path_id": "root.1" } } } ``` `branching.summary.progress_percent` describes the path-specific frame/final-video fan-out. The shared image, speech, music, and scene-video stages remain available in `expressGenerationStatus`. Each `branching.paths[]` entry reports its own `current_stage`, normalized `stages`, and detailed pending/error state. ### Detailed branch preview Use `status_detailed` when the client needs to preview a path before all final videos are ready: ```bash curl "https://api.samsar.one/v2/external/video/status_detailed?request_id=687b4d244d7798b1fbec0123" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` The response includes the detailed manifest at both top-level `branching` and `session.branching`. Both copies add normalized path timelines: ```json { "status_detail_schema": "video_session_preview.v1", "session": { "layers": [ { "id": "687b4e1a4d7798b1fbec1001", "startTime": 0, "endTime": 5, "duration": 5, "preview": { "stage": "ai_video_generation", "type": "video", "url": "https://cdn.example.com/shared-scene-1.mp4" } } ], "audioLayers": [ { "id": "687b4e1a4d7798b1fbec2001", "type": "speech", "status": "COMPLETED", "startTime": 0, "endTime": 5, "duration": 5, "url": "https://cdn.example.com/shared-scene-1.mp3" } ], "branching": { "schema": "branched_video_status.v1", "default_path_id": "root.1", "paths": [ { "path_id": "root.1", "timeline": [ { "sequence_index": 0, "scene_index": 0, "layer_id": "687b4e1a4d7798b1fbec1001", "start_time": 0, "end_time": 5, "duration": 5, "frame_generation": { "status": "COMPLETED", "pending": false } } ], "audio_timeline": [ { "sequence_index": 0, "audio_layer_id": "687b4e1a4d7798b1fbec2001", "connected_layer_id": "687b4e1a4d7798b1fbec1001", "start_time": 0, "end_time": 5, "duration": 5 } ] } ] } } } ``` The media catalog is normalized: `session.layers` and `session.audioLayers` contain each generated asset once, while every path timeline refers to those assets by `layer_id` or `audio_layer_id`. Build an ID lookup table, walk the selected path's timeline in `sequence_index` order, and resolve each reference to its current `preview` asset. The API intentionally omits internal rendered-frame arrays from polling responses. ### Final outputs and interactive switching A branched request reaches top-level `status: "COMPLETED"` only when every required leaf path has a completed final render and URL. At that point: - `result_url` is the deterministic default-path video, preserving the singular-video contract. - `result_urls` contains every leaf video in stable path order. - `branching.outputs.ready` is `true`, and `branching.outputs.paths` maps each `path_id` to its URL. - `branch_results` remains available as a compact compatibility alias for existing clients. Selected terminal fields look like this: ```json { "status": "COMPLETED", "narrative_type": "branched", "default_path_id": "root.1", "result_url": "https://cdn.example.com/root.1.mp4", "result_urls": [ "https://cdn.example.com/root.1.mp4", "https://cdn.example.com/root.2.mp4" ], "branch_results": [ { "path_id": "root.1", "leaf_node_id": "root.1", "ordinal": 0, "status": "COMPLETED", "result_url": "https://cdn.example.com/root.1.mp4" }, { "path_id": "root.2", "leaf_node_id": "root.2", "ordinal": 1, "status": "COMPLETED", "result_url": "https://cdn.example.com/root.2.mp4" } ], "branching": { "schema": "branched_video_status.v1", "status": "COMPLETED", "is_complete": true, "finalized": true, "default_path_id": "root.1", "summary": { "total_paths": 2, "completed_paths": 2, "pending_paths": 0, "failed_paths": 0, "frame_paths_completed": 2, "video_paths_completed": 2, "progress_percent": 100 }, "outputs": { "ready": true, "default_path_id": "root.1", "default_url": "https://cdn.example.com/root.1.mp4", "paths": [ { "path_id": "root.1", "leaf_node_id": "root.1", "ordinal": 0, "is_default": true, "url": "https://cdn.example.com/root.1.mp4" }, { "path_id": "root.2", "leaf_node_id": "root.2", "ordinal": 1, "is_default": false, "url": "https://cdn.example.com/root.2.mp4" } ] } } } ``` For an interactive player, show a choice when playback reaches `tree.choice_points[].switch_at_seconds`. Each option supplies the descendant `leaf_path_ids`; use successive choices to narrow the candidate set to one leaf output. The matching path's `selection_trail` records the exact ordered choices and switch times. Because every leaf video preserves its shared parent prefix, the client can switch to the selected leaf video at the same playback time. If any required path fails, the aggregate request is `FAILED`. Completed sibling URLs remain visible on their path entries and in legacy `branch_results` for diagnosis or recovery, but `branching.outputs.ready` remains `false` and the top-level terminal `result_urls` set is not published as complete. When `webhookUrl` is configured, the terminal success webhook and the persisted external-request response include the same compact `branching`, `branch_results`, `default_path_id`, and ordered `result_urls` fields. Failure webhooks include the normalized branch state for diagnosis but never publish the aggregate `result_urls` set; that set is reserved for successful full completion. For external requests, webhook `request_id` and `session_id` contain the public `extreq_...` ID; `upstream_session_id` contains the internal `VideoSession` ID for server-side correlation. ## samsar-js ```ts const queued = await samsar.createExternalVideoFromNarrative({ narrative_request_id: '687b4127b503e2b4acdf9876', image_model: 'GPTIMAGE2.5', video_model: 'RUNWAYML', }); const status = await samsar.getV2ExternalVideoStatus( queued.data.request_id, ); const manifest = status.data.branching; for (const path of manifest?.paths ?? []) { console.log(path.path_id, path.current_stage, path.result_url); } const detailed = await samsar.getV2ExternalVideoStatusDetailed( queued.data.request_id, ); const previewManifest = detailed.data.session?.branching; const selectedPath = previewManifest?.paths.find( (path) => path.path_id === previewManifest.default_path_id, ); ``` Clients that have not adopted the normalized manifest can continue reading `branch_results`, `result_url`, and `result_urls`. Singular video responses retain their existing shape and do not require branch-aware handling. ## Validation and billing The source must belong to the authenticated user, be terminally successful, have settled narrative billing, and contain valid stored artifacts. Branched sources are revalidated as narrative trees before media work starts. Narrative inference is not run or billed again. The new `VideoSession` is billed at the selected model's full express-render unit price. A singular request uses its linear playback duration. A branched request instead uses the cumulative duration of the normalized layer catalog: ```text branched render credits = selected model unit price × cumulative unique layer duration ``` Shared parent layers count once, every divergent and leaf-only layer counts once, and a shared outro counts once. Complete leaf-path durations are not added together, so rendering multiple leaf videos does not repeatedly bill their shared prefixes. Samsar fixes this cumulative duration when it materializes the canonical layer catalog; later speech or video timing adjustments can change playback timing but do not make different pipeline stages use different billing durations. Optional express add-ons remain separately billable when selected. CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/publications-api.mdx METADATA: title: Publications API description: Publish, edit, and revoke completed Samsar video sessions on the public feed through free API routes. image: img/og/publications-api.png sidebar_position: 6 slug: /publications sidebar_label: Publications API PRIMARY_HEADING: Publications API CONTENT_START # Publications API **Quick facts** - Base URL: `https://api.samsar.one/v1` - Route prefix: `/publications` - Authentication: `Bearer API key/auth token, or customer sub-account key` - Execution: `Synchronous publication controls` - Billing: `No additional charge for CRUD and archive` - Content type: `application/json` Use these endpoints when an external application needs to publish, edit, or revoke a public publication for a completed `samsar-js` video session. The authenticated account must own the target video session. A singular session needs its final video URL; a branched session needs every path render finalized. Singular sessions create the existing single-video `Publication`. A finalized branched session creates an `InteractivePublication` instead. Interactive publication output contains only the public path videos, a first-frame thumbnail for every path, and the media-relative choice graph needed by an interactive player; it does not contain generation diagnostics or billing data. ## Publication fields Samsar infers the final video URL and many defaults from the session. Send the fields your app owns or cannot infer from the session: ```json { "session_id": "66ff...", "title": "Running shoe teaser", "description": "Launch-day vertical cut", "tags": ["launch", "footwear"], "creator_handle": "acme", "slug": "running-shoe-teaser", "image_hash": "optional-image-hash", "splash_image": "https://cdn.example.com/splash.png", "image_model": "GPTIMAGE2.5", "video_model": "RUNWAYML", "original_prompt": "A fast launch video for a new running shoe", "aspect_ratio": "9:16", "has_subtitles": true, "session_language": "en", "language_string": "English" } ``` Alias support: - `session_id`, `sessionId`, `video_session_id`, `videoSessionId`, or `id` - `aspect_ratio` or `aspectRatio` - `creator_handle` or `creatorHandle` - `image_hash` or `imageHash` - `splash_image` or `splashImage` - `image_model` or `imageModel` - `video_model` or `videoModel` - `original_prompt`, `originalPrompt`, or `prompt` - `session_language`, `sessionLanguage`, `language`, or `language_code` - `language_string` or `languageString` - `has_subtitles`, `hasSubtitles`, `enable_subtitles`, or `enableSubtitles` Request bodies can be sent flat or nested under `input`. ## POST /publications/publish Create or update a public publication for a completed session. ```bash curl -X POST https://api.samsar.one/v1/publications/publish \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "session_id": "66ff...", "title": "Running shoe teaser", "description": "Launch-day vertical cut", "tags": ["launch", "footwear"], "creator_handle": "acme" }' ``` Success response: ```json { "created": true, "publication": { "publication_id": "67aa...", "session_id": "66ff...", "video_url": "https://static.samsar.one/video/66ff/final.mp4", "title": "Running shoe teaser", "description": "Launch-day vertical cut", "tags": ["launch", "footwear"], "creator_handle": "acme", "aspect_ratio": "9:16", "has_subtitles": true, "language": "en", "session_language": "en", "language_string": "English" }, "session": { "session_id": "66ff...", "is_published": true, "published_publication_id": "67aa...", "published_video_url": "https://static.samsar.one/video/66ff/final.mp4" } } ``` ## POST /publications/edit Edit an existing publication for a session. Omitted fields keep their current publication values. ```bash curl -X POST https://api.samsar.one/v1/publications/edit \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "session_id": "66ff...", "title": "Running shoe teaser - updated", "tags": ["launch", "footwear", "campaign"] }' ``` `404` is returned when the session exists but has no publication yet. ## POST /publications/revoke Remove a session publication from the public feed and clear the published fields on the session. ```bash curl -X POST https://api.samsar.one/v1/publications/revoke \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "session_id": "66ff..." }' ``` Success response: ```json { "revoked": true, "publication_id": "67aa...", "session": { "session_id": "66ff...", "is_published": false } } ``` ## REST aliases The same operations are also available as session-centric routes: - `POST /publications/session/:sessionId` - `PATCH /publications/session/:sessionId` - `DELETE /publications/session/:sessionId` ## Public interactive publication feed These read routes are unauthenticated and free: - `GET https://api.samsar.one/interactive_publications` - `GET https://api.samsar.one/interactive_publications/:publicationId` - Versioned aliases are available under `/v1/interactive_publications`. The list supports `limit` (default `50`, maximum `200`) and an opaque `cursor` returned by the previous page: ```bash curl "https://api.samsar.one/interactive_publications?limit=20" ``` ```json { "items": [{ "id": "67ab...", "type": "InteractiveVideo", "schema": "interactive_publication.v1", "title": "Fork in the road", "description": "Choose the hero's next move.", "tags": ["interactive", "adventure"], "creatorHandle": "acme", "datePublished": "2026-07-19T00:00:00.000Z", "thumbnailUrl": "https://static.samsar.one/published/66ff/interactive/revisions/8f4d.../paths/cm9vdC4x/thumbnail.png", "aspectRatio": "16:9", "inLanguage": "en", "hasSubtitles": true, "manifest": { "schema": "interactive_video_manifest.v1", "default_path_id": "root.1", "timing": { "origin": "media", "unit": "seconds" }, "tree": { "root_node_id": "root", "choice_points": [{ "branch_point_id": "choice-root", "parent_node_id": "root", "switch_at_seconds": 8, "options": [{ "child_node_id": "root.1", "path_name": "Open the door", "path_description": "Continue through the doorway.", "leaf_path_ids": ["root.1"] }] }] }, "outputs": { "paths": [{ "path_id": "root.1", "contentUrl": "https://static.samsar.one/published/66ff/interactive/revisions/8f4d.../paths/cm9vdC4x/video.mp4", "thumbnailUrl": "https://static.samsar.one/published/66ff/interactive/revisions/8f4d.../paths/cm9vdC4x/thumbnail.png", "encodingFormat": "video/mp4", "duration": 24, "is_default": true }] } } }], "nextCursor": null, "hasMore": false, "totalCount": 1 } ``` The `timing`, `tree`, and `outputs.paths` layout follows the completed branched response from `status_detailed`, so clients can reuse the same choice-graph logic. `contentUrl`, `thumbnailUrl`, and `encodingFormat` use standard media-resource field names. `duration` and `switch_at_seconds` are media-relative seconds. A publication is withheld if any path video or thumbnail is unavailable publicly; clients never receive a partial choice graph. ## Error responses - `400` missing or invalid `session_id` - `401` invalid API key, customer sub-account key, or auth token - `403` the session does not belong to the authenticated actor - `404` session or publication not found - `409` the session has no final video URL yet ## samsar-js ```ts const samsar = new SamsarClient({ apiKey: process.env.SAMSAR_API_KEY! }); await samsar.publishPublication({ session_id: '66ff...', title: 'Running shoe teaser', tags: ['launch', 'footwear'], }); await samsar.editPublication({ session_id: '66ff...', title: 'Running shoe teaser - updated', }); await samsar.revokePublication('66ff...'); const page = await samsar.listInteractivePublications({ limit: 20 }); const interactivePublication = await samsar.getInteractivePublication(page.data.items[0].id); ``` CONTENT_END === DOCUMENT END === === DOCUMENT START === SOURCE_PATH: docs/v2-api.mdx METADATA: title: V2 API description: Use additive v2 routes that unify internal, app-key, and external-user clients across media and account workflows. image: img/og/v2-api.png slug: /v2 sidebar_label: V2 API PRIMARY_HEADING: V2 API CONTENT_START # V2 API Start with hosted **samsar-js**, use the [model matrix](/models) to compare capabilities, and follow the [adapter guide](/adapters) for standalone deployments. The route map below preserves the API-specific request contracts. **Quick facts** - Base URL: `https://api.samsar.one` - Route prefix: `/v2 · alias /api/v2` - Authentication: `Internal, app-key, or external-user actor` - Execution: `Unified synchronous and asynchronous workflows` - Billing: `Delegates to the canonical route's existing billing rules` - Content type: `application/json` `/v2` is an additive API surface. `/v1` is not deprecated. The same router is also available under `/api/v2` for clients that use the longer API prefix. The `/v2` routes are designed for clients where either an internal Samsar account or an external user can be authenticated in the same UI. Each route resolves the authenticated actor and routes the request through the existing internal or external-user handling path so the same validation, pricing, and credit deduction rules continue to apply. ## Route map The tables favor canonical paths. Compatibility aliases are called out only when they are useful during a migration; use the endpoint sections below for request contracts and status behavior. ### Core session and request routes | Route | Purpose | | --- | --- | | `GET /v2/health` | Check V2 service health without authentication. | | `POST /v2/session` | Create a V2 session. | | `POST /v2/create_login_token` | Create a login token for authenticated flows. | | `GET /v2/requests` | List requests visible to the authenticated actor. | | `GET /v2/status` | Poll request or session status. | | `GET /v2/status_detailed` | Poll status plus normalized video-session preview data. | | `POST /v2/status_detailed` | POST alias for detailed status checks. | ### Credits and billing routes | Route | Purpose | | --- | --- | | `GET /v2/credits` | Fetch resolved actor credits. | | `POST /v2/credits/recharge` | Create an internal recharge flow. | | `POST /v2/credits/grant` | Grant credits to an external user. | | `GET /v2/payment_status` | Fetch payment status for a resolved actor. | | `POST /v2/payment_status` | Check payment status with a request body. | ### User account routes | Route | Purpose | | --- | --- | | `POST /v2/user/recharge_credits` | Create or top up a Samsar user by email. | | `GET /v2/user/recharge_credits` | GET alias for user credit recharge. | | `POST /v2/user/create_external_user` | Create or update an external user mapped to the authenticated internal user. | | `POST /v2/user/refresh_token` | Rotate a user refresh token. | | `GET /v2/user/refresh_token` | GET alias for user refresh token rotation. | | `GET /v2/user/credits` | Fetch authenticated user credits. | | `GET /v2/user/usage/logs` | Fetch authenticated user usage logs. | | `GET /v2/user/payment_status` | Fetch user payment status. | | `POST /v2/user/payment_status` | Check user payment status with a request body. | ### App key routes | Route | Purpose | | --- | --- | | `POST /v2/users/app_key` | Create a long-running app key. | | `GET /v2/users/app_key` | Fetch active app key metadata. | | `DELETE /v2/users/app_key` | Revoke the active app key. | | `POST /v2/users/app_key/refresh` | Rotate an app key. | | `GET /v2/users/app_key/refresh` | GET alias for app key rotation. | ### Assistant and utility routes | Route | Purpose | | --- | --- | | `POST /v2/assistant/set_system_prompt` | Set an assistant system prompt. | | `POST /v2/assistant/completion` | Create an assistant completion. | | `POST /v2/external/chat` | Create an OpenAI-compatible external inference completion. | | `POST /v2/external/chat/completions` | Alias for external inference completions. | | `POST /v2/external/assistant` | Assistant-named alias for external chat completions. | | `POST /v2/external/assistant/completions` | Assistant-named OpenAI-compatible completion alias. | | `GET /v2/external/chat/status` | Poll an external assistant request by `request_id` or `client_request_id`. | | `GET /v2/external/chat/completions/status` | Status alias for external assistant requests. | | `GET /v2/external/assistant/status` | Assistant-named status alias. | | `POST /v2/external/moderation` | Create a no-charge external moderation check. | | `POST /v2/external/moderations` | Alias for external moderation checks. | | `GET /v2/external/api_key/validate` | Validate a Samsar provider API key. | | `GET /v2/external/providers/capabilities` | Read provider, model, and action capabilities. | | `POST /v2/external/providers/validate` | Validate deployment provider credentials. | | `POST /v2/external/narrative/create_single` | Create a singular structured narrative. | | `POST /v2/external/narrative/create_branching` | Create a branching structured narrative. | | `GET /v2/external/narrative/status` | Poll a singular or branching narrative request. | | `POST /v2/external/audio/text_to_speech` | Create hosted speech for 1 credit/request. | | `POST /v2/external/audio/text_to_music` | Create an API-key billed hosted text-to-music request. | | `POST /v2/external/audio/text_to_sound_effect` | Create a hosted sound effect for 1 credit/request. | | `POST /v2/external/audio/transcript_align` | Align transcript audio with usage-based per-minute billing. | | `GET /v2/external/audio/status` | Poll hosted external audio request status. | | `POST /v2/external/embeddings` | Create 1536-dimension external embeddings with `text-embedding-3-small`. | | `POST /v2/external/image/text_to_image` | Generate hosted images; `/generate` and `/generations` are aliases. | | `POST /v2/external/image/assign_title` | Generate an image title with usage-based billing. | | `POST /v2/external/image/enhance` | Enhance a hosted image. | | `POST /v2/external/image/remove_branding` | Remove branding from a hosted image. | | `POST /v2/external/image/add_image_set` | Extend a hosted image set. | | `GET /v2/external/image/status` | Poll a hosted image request. | | `POST /v2/utils/assistant_session` | Create or resolve an assistant session. | | `POST /v2/utils/usage_charge` | Apply a usage charge. | | `POST /v2/generate_embeddings_from_plain_text` | Create embeddings from cleaned plain text. | ### Media generation routes | Route | Purpose | | --- | --- | | `POST /v2/upload_image_data` | Upload image data for later generation routes. | | `POST /v2/image/assign_title` | Assign a short SEO-friendly image title. Returns `{ "content": "..." }` synchronously and uses usage-based credits. | | `POST /v2/images/assign_title` | Alias for `POST /v2/image/assign_title`. | | `POST /v2/image/enhance` | Enhance or upscale an image using the same pricing as `/v1/image/enhance`. | | `POST /v2/image/remove_branding` | Remove watermark, logo, or brand text using the same pricing as `/v1/image/remove_branding`. | | `POST /v2/image/add_image_set` | Extend an image set using the same pricing as `/v1/image/add_image_set`. | | `POST /v2/image/text_to_image` | Generate images for 15 credits per output. | | `POST /v2/text_to_video` | Create a video from text input. | | `POST /v2/image_list_to_video` | Create a video from image URLs. | | `POST /v2/video/step/text_to_video` | Create a step-controlled text-to-video request. | | `POST /v2/video/step/image_to_video` | Create a step-controlled image-to-video request. | | `POST /v2/video/step/lip_sync` | Create a step-controlled lip-sync request. | | `POST /v2/video/step/sound_effect` | Create a step-controlled sound-effect request. | | `GET /v2/video/step/:request_id/status` | Poll step-controlled request status and completed resources. | | `GET /v2/video/step/:request_id/status_detailed` | Poll step status plus normalized preview data for completed media. | | `POST /v2/video/step/:request_id/status_detailed` | POST alias for detailed step status checks. | | `POST /v2/video/step/:request_id/process_next` | Continue a completed step-controlled request to the next stage. | | `POST /v2/external/video/text_to_video` | Create an API-key billed hosted text-to-video request for remote deployments. | | `POST /v2/external/video/image_to_video` | Create an API-key billed hosted image-to-video request for remote deployments. | | `POST /v2/external/video/direct_image_to_video` | Generate hosted video directly from a public image URL. | | `POST /v2/external/video/narrative_to_video` | Start hosted media generation from a completed singular or branched NarrativeRequest. | | `POST /v2/external/video/lip_sync` | Create an API-key billed hosted lip-sync request from public media URLs. | | `POST /v2/external/video/sound_effect` | Create an API-key billed hosted sound-effect request from a public video URL. | | `GET /v2/external/video/status` | Poll hosted external video request status. | | `GET /v2/external/video/status_detailed` | Poll hosted external video detailed status and preview data. | | `POST /v2/text_to_interactive_video` | Generate an interactive video workflow. | | `POST /v2/external/video/text_to_interactive_video` | External-provider alias for interactive video generation. | | `POST /v2/text_to_interactive_video/session` | Create an interactive-video draft session. | | `POST /v2/translate_video` | Translate an existing video. | | `POST /v2/retranslate_video` | Alias for translating an existing video. | | `POST /v2/add_outro_image` | Add an outro image to an existing video. | | `POST /v2/update_outro_image` | Update an existing outro image. | | `POST /v2/update_footer_image` | Update or remove an existing video footer CTA. | | `POST /v2/add_subtitles` | Add subtitles; `/v2/video/add_subtitles` is an alias. | | `POST /v2/remove_subtitles` | Remove subtitles; `/v2/video/remove_subtitles` is an alias. | | `POST /v2/join_videos` | Join videos into one output. | | `POST /v2/video/clone` | Deep-copy a completed video session and render a new final video URL. | | `POST /v2/regenerate_avatar` | Regenerate the narrator avatar; `/v2/video/regenerate_avatar` is an alias. | | `POST /v2/reroll-layers` | Reroll selected 1-based layers from image generation, then rerender the session. | | `POST /v2/video/reroll-layers` | Alias for `POST /v2/reroll-layers`. | | `POST /v2/cancel_render` | Cancel active render work. | | `POST /v2/pause_render` | Pause active render work. | | `POST /v2/resume_render` | Resume paused render work. | ### Publishing routes | Route | Purpose | | --- | --- | | `POST /v2/archive` | Archive a generated asset. | | `POST /v2/publish` | Publish a generated asset. | | `POST /v2/interactive_publication/generate_meta` | Generate interactive publication metadata at 1.5×; requires a user auth token. | ## Authentication behavior - Internal API key or internal auth token with no `external_user` payload uses the internal account and deducts internal generation credits. - APP_KEY authentication uses `Authorization: AppKey ` plus `x-app-secret: `. It is a third internal-account auth method for `/v2` routes after the APP_KEY has been created with a normal API key or user auth token. - Internal API key/auth token plus an `external_user` payload uses the external-user path and deducts that external user's credits. - External-user auth tokens, internal API keys paired with an external-user API key, and customer sub-account API keys use the external-user path. - Repeated video routes and status routes resolve `request_id` / `session_id` through external request records and `GlobalSession` records, so either returned `extreq_...` ids, normalized external ids, or internal video session ids can be used. - `POST /v2/interactive_publication/generate_meta` is the exception to the general credential rules: it accepts a user `auth_token`, not an API key or APP_KEY. ## External user registration Use `POST /v2/user/create_external_user` to create or update an external user record under the authenticated internal Samsar user. Authenticate with an internal API key, internal user auth token, or APP_KEY. External-user auth tokens and customer sub-account auth cannot create new external users. The route accepts a `unique_key` for the external user, such as a wallet address, email address, or your platform's user id. If `unique_key` is omitted, Samsar uses `external_user_id` as the `unique_key`. Future V2 calls can reference that external user with `unique_key`. **Request** ```bash curl -X POST "https://api.samsar.one/v2/user/create_external_user" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "unique_key": "wallet:0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "email": "member@example.com", "display_name": "Member Name" }' ``` You can also send `provider`, `external_user_id`, `external_app_id`, `external_company_id`, `external_account_id`, `username`, `avatar_url`, `user_type`, and `metadata`. If `provider` is omitted, Samsar stores the record under the default V2 provider. **Success response (201)** ```json { "unique_key": "wallet:0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "provider": "samsar_v2", "external_user_id": "wallet:0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "external_app_id": null, "external_user": { "id": "65f...", "provider": "samsar_v2", "external_user_id": "wallet:0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "unique_key": "wallet:0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "email": "member@example.com", "display_name": "Member Name", "generation_credits": 0 }, "reference": { "provider": "samsar_v2", "unique_key": "wallet:0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "external_user_id": "wallet:0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "external_app_id": null } } ``` If a matching `unique_key` or external identity already belongs to another internal user, the route returns `409`. ## User credits and tokens Use `/v2/user/recharge_credits` when a client needs to create or top up a Samsar user account by email without first holding an API key. The route accepts `POST` or `GET`; `POST` is recommended. **Request** ```bash curl -X POST "https://api.samsar.one/v2/user/recharge_credits" \ -H "Content-Type: application/json" \ -d '{ "amount": 25, "email": "customer@example.com", "redirect_url": "https://example.com/samsar/callback" }' ``` Fields: - `amount` is a positive USD amount. Recharge credits use the deployment's configured credits-per-dollar rate, which defaults to **100 credits per USD**. - `email` must be a valid email address. - `redirect_url` must be HTTPS. The payment success webhook sends the token callback to this URL. **Success response (200)** ```json { "url": "https://checkout.stripe.com/c/pay/cs_test_...", "checkoutSessionId": "cs_test_...", "paymentStatusEndpoint": "/v2/user/payment_status", "amount": 25, "amountCents": 2500, "credits": 2500, "currency": "USD", "redirectUrl": "https://example.com/samsar/callback" } ``` On Stripe payment success, Samsar: - creates a user for `email` if one does not already exist, - sends the new user a Samsar Studio email to confirm email and set a password, - applies the purchased credits idempotently, - mints a 10-day `authToken`, - mints a rotating `refreshToken`, - calls `redirect_url` with `authToken`, `refreshToken`, and `expiryDate` query parameters. The webhook first attempts `GET ?authToken=...&refreshToken=...&expiryDate=...`. If that returns `404`, it retries `POST` to the same URL with the same query parameters and a JSON body containing the token and payment metadata. Clients should store `authToken`, `refreshToken`, and `expiryDate`. Poll for a replacement token when the app opens within 3 days of `expiryDate`. **Refresh token rotation** ```bash curl -X POST "https://api.samsar.one/v2/user/refresh_token" \ -H "Content-Type: application/json" \ -d '{"refreshToken": "srf_..."}' ``` **Success response (200)** ```json { "tokenType": "Bearer", "authToken": "eyJ...", "refreshToken": "srf_new...", "expiryDate": "2026-05-08T12:00:00.000Z", "expiresInSeconds": 864000, "refreshTokenExpiresAt": "2026-07-27T12:00:00.000Z" } ``` `/v2/user/refresh_token` consumes the old refresh token and returns both a new 10-day auth token and a new refresh token. Reusing the previous refresh token returns `401`. Use the returned `authToken` as `Authorization: Bearer ` for `/v2/user/credits`, `/v2/user/usage/logs`, `/v2/user/payment_status`, and the rest of the internal `/v2` routes. ## App key credentials Use `/v2/users/app_key` to mint one long-running APP_KEY for a user after the user is already authenticated with a Samsar API key or user auth token. `/v2/user/app_key` is accepted as an alias. The client supplies a custom `secret`. Samsar returns the APP_KEY once, stores only an HMAC hash of the APP_KEY, and stores only a bcrypt hash of the secret. If an active APP_KEY already exists for the user, creation returns `409`. **Create APP_KEY** ```bash curl -X POST "https://api.samsar.one/v2/users/app_key" \ -H "Authorization: Bearer $SAMSAR_AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "secret": "a-client-generated-secret-at-least-32-chars" }' ``` **Success response (201)** ```json { "app_key": "sapp_...", "appKey": "sapp_...", "token_type": "AppKey", "expires_at": "2027-04-28T12:00:00.000Z", "app_key_record": { "id": "appkey_...", "appKeyPrefix": "sapp_abc12", "appKeyLast4": "9xyz", "status": "active", "expiresAt": "2027-04-28T12:00:00.000Z", "authScheme": "AppKey", "authHeader": "Authorization: AppKey ", "secretHeader": "x-app-secret" } } ``` Use the APP_KEY on authenticated `/v2` routes: ```bash curl "https://api.samsar.one/v2/user/credits" \ -H "Authorization: AppKey $SAMSAR_APP_KEY" \ -H "x-app-secret: $SAMSAR_APP_SECRET" ``` If the APP_KEY expires, rotate it with the APP_KEY and APP_SECRET. The old APP_KEY is consumed and replaced; the APP_SECRET stays the same. ```bash curl -X POST "https://api.samsar.one/v2/users/app_key/refresh" \ -H "Content-Type: application/json" \ -d '{ "app_key": "sapp_...", "secret": "a-client-generated-secret-at-least-32-chars" }' ``` `GET /v2/users/app_key` returns APP_KEY metadata for an authenticated user without returning the secret or full key. `DELETE /v2/users/app_key` revokes the active APP_KEY so the user can create a replacement. ## Media generation `POST /v2/text_to_video` and `POST /v2/image_list_to_video` accept the corresponding [`/v1/video/*` request shapes](/video), either nested under `input` or sent as a flat JSON payload. | Setting | Accepted values | | --- | --- | | `inference_model` / `inferenceModel` | `gpt-6-astra` (default), `gemini-3.1-pro`, `QWEN3.8` (Qwen 3.8 Max for text and vision), `kimi-k3` | | `backingtrack_model` / `music_provider` | `LYRIA3`, `ELEVENLABS_MUSIC` | | `tts_model` / `tts_provider` | `ELEVENLABS`, `OPENAI`, `GOOGLE` | | `speakerOptions` / `speaker_options` | Provider-specific speaker lists and Google speaker details | `video_model_sub_type` is not part of the V2 request shape; put style direction in `prompt`. See [Models & pricing](/pricing) for current model keys, per-second rates, and runtime-availability guidance. **Internal billing example** ```bash curl -X POST "https://api.samsar.one/v2/image_list_to_video" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "image_urls": ["https://cdn.example.com/a.png", "https://cdn.example.com/b.png"], "video_model": "RUNWAYML", "express_cta_generation": true, "cta_url": "https://example.com/book", "limit_single_narrator": true } }' ``` **External-user billing example** ```bash curl -X POST "https://api.samsar.one/v2/image_list_to_video" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "external_user": { "provider": "guidestination", "unique_key": "traveler_123", "external_user_id": "traveler_123" }, "input": { "image_urls": ["https://cdn.example.com/a.png", "https://cdn.example.com/b.png"], "video_model": "KLINGIMGTOVID3PRO", "add_narrator_avatar": true, "add_footer_animation": true, "footer_metadata": [ { "url": "https://example.com/stop/1", "title": "Stop 1" }, { "url": "https://example.com/stop/2", "title": "Stop 2" } ] } }' ``` For `/v2/image_list_to_video`, choose one outro mode and keep its fields together: | Mode | Fields | Notes | | --- | --- | --- | | QR CTA outro | `cta_url` | Samsar generates the centered QR outro. | | Center-image CTA | `outro_cta_image` | Use `{ "top_text", "middle_image", "bottom_text" }`. | | Express CTA | `express_cta_generation: true`, one `cta_url` | Generates per-scene footer titles and outro text; adds 1 credit/sec. | `add_outro_animation` defaults to `true`. `limit_single_narrator` keeps one narrator identity; `add_narrator_avatar` enables it automatically and adds **4 credits/sec**. The V2 route uses the same model defaults and rates as the V1 image-list route, summarized in the video model rate table. ### V2 image edit routes The `/v2/image/enhance`, `/v2/image/remove_branding`, and `/v2/image/add_image_set` routes are thin aliases over the existing image API handlers and use the same image edit queue, validation, credit headers, and status polling behavior as `/v1/image/*`. Request bodies may be sent either directly or inside the standard V2 `{ "input": { ... } }` wrapper. Pricing is unchanged from the Image API: `POST /v2/image/enhance` charges 11 credits at `0.5k`, 15 credits at `1k`, 22 credits at `2k`, and 29 credits at `4k`; `POST /v2/image/remove_branding` charges 15 credits per request; `POST /v2/image/add_image_set` charges `15 × requested_images`. ### Step-controlled video generation Use the `/v2/video/step/*` routes when a client needs step-aware generation and detailed timeline preview data. Step requests use the same express video models and stage billing rules as `/v2/text_to_video` and `/v2/image_list_to_video`. Step routes support two render modes: - **1-step express render**: set `auto_render_full_video: true` and `manual_step_stages: []`. The request generates the full video without waiting for user approval before `ai_video_generation`. - **2-step render**: set `auto_render_full_video: false` and `manual_step_stages: ["ai_video_generation"]`. The request pauses after image/audio assets are ready, then waits for `process_next` before the image-to-video stage. When your request body uses the standard `{ "input": { ... } }` wrapper, include these fields inside `input`. Clients may also send the same fields at the top level for compatibility. Only call `process_next` when the status response says `waiting_for_process_next`, `requires_user_action`, or `can_process_next` is true. Step-controlled routes are internal-account routes. Authenticate with a Samsar API key, user auth token, or APP_KEY. **Create from text** ```bash curl -X POST "https://api.samsar.one/v2/video/step/text_to_video" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "auto_render_full_video": true, "manual_step_stages": [], "input": { "prompt": "A 20 second launch teaser for a new travel app", "image_model": "GPTIMAGE2.5", "video_model": "RUNWAYML", "duration": 20, "aspect_ratio": "16:9", "enable_subtitles": true, "auto_render_full_video": true, "manual_step_stages": [] }, "webhookUrl": "https://example.com/video-webhook" }' ``` **Create from an image** ```bash curl -X POST "https://api.samsar.one/v2/video/step/image_to_video" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "auto_render_full_video": false, "manual_step_stages": ["ai_video_generation"], "input": { "image_url": "https://cdn.example.com/product-frame.png", "prompt": "Turn this product frame into a cinematic ad", "video_model": "KLINGIMGTOVID3PRO", "aspect_ratio": "16:9", "enable_subtitles": true, "auto_render_full_video": false, "manual_step_stages": ["ai_video_generation"] } }' ``` The response includes the normal `request_id` / `session_id` plus a `step` object. For 1-step requests, poll the detailed step status route until the final video is complete. For 2-step requests, poll until the response asks for user action, inspect `current_step_resources`, optionally edit resources through the relevant session/resource APIs, then call `process_next`. ```bash curl "https://api.samsar.one/v2/video/step/65f1234567890abcdef12345/status" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` ```json { "request_id": "65f1234567890abcdef12345", "session_id": "65f1234567890abcdef12345", "status": "COMPLETED", "step_status": "COMPLETED", "current_step": "image_generation", "current_step_label": "Images", "next_step": "ai_video_generation", "waiting_for_process_next": true, "requires_user_action": true, "can_process_next": true, "manual_step_stages": ["ai_video_generation"], "process_next_url": "/v2/video/step/process_next", "current_step_resources": { "step": "image_generation", "label": "Images", "status": "COMPLETED", "completed_at": "2026-05-07T10:30:00.000Z", "resources": { "layers": [ { "index": 0, "prompt": "Wide product shot with cinematic lighting", "selected_image_url": "https://cdn.samsar.one/generated/layer-0.png" } ] } }, "completed_step_resources": { "prompt_generation": { "completed_at": "2026-05-07T10:27:00.000Z", "resources": { "input_prompt": "A 20 second launch teaser for a new travel app", "scenes": [] } }, "image_generation": { "completed_at": "2026-05-07T10:30:00.000Z", "resources": { "layers": [] } } } } ``` For preview playback before the final render, use `status_detailed`. It returns the same top-level status fields plus `status_detail_schema` and a normalized `session` object. The session payload includes only the data needed to reconstruct the current playable stage on the client: scene `layers`, `audioLayers`, `globalAudioLayers`, `globalVideos`, and final `result` metadata when available. Layer timing is normalized to `startTime` and `endTime`; the response does not expose internal fields such as `durationOffset`. ```bash curl "https://api.samsar.one/v2/video/step/65f1234567890abcdef12345/status_detailed" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` Example response after `ai_video_generation` has completed, before lip sync, effects, or the final render: ```json { "request_id": "65f1234567890abcdef12345", "session_id": "65f1234567890abcdef12345", "status": "COMPLETED", "type": "video", "provider": "RUNWAYML", "step_status": "COMPLETED", "current_step": "ai_video_generation", "current_step_label": "AI video", "next_step": "lip_sync_generation", "waiting_for_process_next": true, "process_next_url": "/v2/video/step/process_next", "status_detail_schema": "video_session_preview.v1", "session": { "id": "65f1234567890abcdef12345", "requestId": "65f1234567890abcdef12345", "type": "video", "routeType": "step", "aspectRatio": "16:9", "framesPerSecond": 30, "duration": 12, "language": "en", "hasSubtitles": true, "hasFooter": false, "inputPrompt": "A 12 second launch teaser for a new travel app", "generationType": "text_to_video", "provider": "RUNWAYML", "currentStage": "lip_sync_generation", "previewStage": "ai_video_generation", "completedStages": [ "prompt_generation", "image_generation", "speech_generation", "music_generation", "audio_generation", "ai_video_generation" ], "stages": { "prompt_generation": "COMPLETED", "image_generation": "COMPLETED", "speech_generation": "COMPLETED", "music_generation": "COMPLETED", "audio_generation": "COMPLETED", "ai_video_generation": "COMPLETED", "lip_sync_generation": "INIT", "sound_effect_generation": "INIT", "video_generation": "INIT" }, "layers": [ { "index": 0, "id": "65f1234567890abcdef0001", "startTime": 0, "endTime": 6, "duration": 6, "prompt": "Wide product shot with cinematic lighting", "videoPrompt": "Slow push-in over the mobile booking screen", "image": { "status": "COMPLETED", "url": "https://cdn.samsar.one/generated/scene-0.png" }, "aiVideo": { "status": "COMPLETED", "url": "https://cdn.samsar.one/generated/scene-0-ai.mp4" }, "preview": { "stage": "ai_video_generation", "type": "video", "url": "https://cdn.samsar.one/generated/scene-0-ai.mp4" } }, { "index": 1, "id": "65f1234567890abcdef0002", "startTime": 6, "endTime": 12, "duration": 6, "prompt": "Traveler checking into a hotel from the app", "videoPrompt": "Handheld travel lifestyle shot with warm daylight", "image": { "status": "COMPLETED", "url": "https://cdn.samsar.one/generated/scene-1.png" }, "aiVideo": { "status": "COMPLETED", "url": "https://cdn.samsar.one/generated/scene-1-ai.mp4" }, "preview": { "stage": "ai_video_generation", "type": "video", "url": "https://cdn.samsar.one/generated/scene-1-ai.mp4" } } ], "audioLayers": [ { "index": 0, "type": "speech", "status": "COMPLETED", "startTime": 0, "endTime": 6, "duration": 6, "url": "https://cdn.samsar.one/generated/scene-0-speech.mp3", "speaker": "alloy", "connectedLayerIndex": 0 }, { "index": 1, "type": "speech", "status": "COMPLETED", "startTime": 6, "endTime": 12, "duration": 6, "url": "https://cdn.samsar.one/generated/scene-1-speech.mp3", "speaker": "alloy", "connectedLayerIndex": 1 } ], "globalAudioLayers": [ { "index": 0, "type": "music", "status": "COMPLETED", "startTime": 0, "endTime": 12, "duration": 12, "url": "https://cdn.samsar.one/generated/music-bed.mp3", "volume": 0.25 } ], "result": { "hasSubtitles": true, "hasFooter": false, "language": "en" } } } ``` Continue to the next stage: ```bash curl -X POST "https://api.samsar.one/v2/video/step/65f1234567890abcdef12345/process_next" \ -H "Authorization: Bearer $SAMSAR_API_KEY" ``` Step status values: - `INIT`: the step request exists but has not started its first tracked stage. - `PENDING`: the current stage is running. - `COMPLETED`: the final video stage is complete, or a 2-step request is paused at a manual checkpoint and waiting for `process_next`. - `FAILED`: the request failed. The stage sequence is `prompt_generation`, `image_generation`, `speech_generation`, `music_generation`, `ai_video_generation`, `lip_sync_generation`, `sound_effect_generation`, `narrator_avatar_generation`, and `video_generation`. **Translate example** ```bash curl -X POST "https://api.samsar.one/v2/translate_video" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "videoSessionId": "vid_a1b2c3d4", "language": "es", "enable_subtitles": false, "translate_outro": true, "translate_footer": true }, "webhookUrl": "https://example.com/video-webhook" }' ``` `/v2/translate_video` and `/v2/retranslate_video` accept the same payload as `/v1/video/translate_video`. `enable_subtitles` defaults to `false`; when true, Samsar regenerates translated transcript/subtitle text. `translate_outro` and `translate_footer` default to `true` and localize stored generated-outro CTA text and footer CTA text when those metadata fields exist. The translate route no longer accepts `outro_image_url`; use `/v2/update_outro_image` for outro replacement. **Footer update example** ```bash curl -X POST "https://api.samsar.one/v2/update_footer_image" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "videoSessionId": "vid_a1b2c3d4", "cta_text": "Scan to book", "cta_logo": "https://cdn.example.com/logo-white.png", "cta_url": "https://example.com/book" } }' ``` Set `remove_footer: true` in the `input` object to clone the session and remove all scene footers instead. Like `/v1/video/update_footer_image`, the V2 route queues only footer/frame/video work and charges 75 credits/request. **Deep clone example** ```bash curl -X POST "https://api.samsar.one/v2/video/clone" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "videoSessionId": "vid_a1b2c3d4" }, "webhookUrl": "https://example.com/video-webhook" }' ``` `/v2/video/clone` is free for an authenticated user who owns the source session. It copies the session data and local generated assets into a new session, marks all upstream generation stages complete, and queues only the final `video_generation` stage so the clone receives its own rendered video path and URL. Poll `/v2/status?request_id=` until it returns the standard completed video payload with `result_url`, `has_subtitles`, and `result_language`. **Reroll layers example** ```bash curl -X POST "https://api.samsar.one/v2/reroll-layers" \ -H "Authorization: Bearer $SAMSAR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "videoSessionId": "vid_a1b2c3d4", "layer_indexes": [3, 6, 11] }, "webhookUrl": "https://example.com/video-webhook" }' ``` `/v2/reroll-layers` and `/v2/video/reroll-layers` update the same owned session in place. The selected 1-based layers restart from image generation, then required AI-video and lip-sync/sound-effect work runs for those layers before frame/outro/final video generation reruns for the whole session. Credits are the selected image-generation costs plus the selected layers' AI-video stage credits; send `quote_only: true` in `input` to estimate without charging. ### External video provider routes Use `/v2/external/video/*` when a remote deployment needs hosted Samsar generation through a Samsar API key. These are standalone provider-style endpoints, separate from `/v2/video/step/*`. Supported routes: - `POST /v2/external/video/text_to_video` - `POST /v2/external/video/image_to_video` - `POST /v2/external/video/direct_image_to_video` - `POST /v2/external/video/narrative_to_video` - `POST /v2/external/video/text_to_interactive_video` - `POST /v2/external/video/lip_sync` - `POST /v2/external/video/sound_effect` - `GET` or `POST /v2/external/video/status` - `GET` or `POST /v2/external/video/{request_id}/status` - `GET` or `POST /v2/external/video/status_detailed` - `GET` or `POST /v2/external/video/{request_id}/status_detailed` `text_to_video` accepts the same express text input shape as `/v2/text_to_video`. `image_to_video` requires exactly one public `image_url` (or a one-item `image_urls` array) plus `video_model`. `narrative_to_video` accepts a completed singular or branched `narrative_request_id`, inherits its prompt and duration, and skips prompt generation; see [Render a narrative as video](/narrative/render-video). `lip_sync` requires public `video_url` and `audio_url` values. `sound_effect` requires a public `video_url` and prompt. Raw media bytes, data URLs, local filesystem paths, localhost URLs, and private network URLs are not accepted for hosted external video-to-video routes. For a branched narrative-to-video request, both status routes return a normalized `branching` manifest. The normal status response is compact and contains tree choices, aggregate path progress, per-path frame/video status, and any completed path result. Detailed status adds path timelines that reference the shared `session.layers` and `session.audioLayers` catalogs by ID; it does not return internal frame lists. A top-level `COMPLETED` result is published only after every required leaf video is ready. ## Assistant and utilities Assistant and utility routes use the same actor resolution behavior as the rest of `/v2`. Use them when a mixed internal/external-user client needs assistant completions, assistant sessions, usage charges, or cleaned-text embeddings through one API prefix. Supported assistant and utility routes: - `POST /v2/assistant/set_system_prompt` - `POST /v2/assistant/completion` - `POST /v2/external/chat` - `POST /v2/external/chat/completions` - `POST /v2/external/assistant` - `POST /v2/external/assistant/completions` - `GET /v2/external/chat/status` - `GET /v2/external/chat/completions/status` - `GET /v2/external/assistant/status` - `POST /v2/external/moderation` - `POST /v2/external/moderations` - `POST /v2/external/audio/text_to_speech` - `POST /v2/external/audio/text_to_music` - `POST /v2/external/audio/text_to_sound_effect` - `POST /v2/external/audio/transcript_align` - `GET` or `POST /v2/external/audio/status` - `POST /v2/external/embeddings` - `POST /v2/utils/assistant_session` - `POST /v2/utils/usage_charge` - `POST /v2/generate_embeddings_from_plain_text` External chat and assistant aliases accept OpenAI-compatible text and vision messages and use the Chat API external inference pricing table. They execute synchronously by default. Set `response_mode: "polling"` for an HTTP `202` queued response, persist `request_id`, and poll one of the status routes until `COMPLETED` or `FAILED`. A stable `client_request_id` makes submission retry-safe after a network reset and can also be used for status lookup. The default execution timeout is 10 minutes and request records are retained for 24 hours. See [External assistant and chat completions](/external-requests#external-assistant-and-chat-completions) for the complete request and response contract. External moderation routes accept OpenAI-compatible moderation input and do not charge credits. Hosted audio charges **1 credit/request** for text to speech or sound effects and **2 credits/request** for text to music; music accepts `ELEVENLABS_MUSIC` and `LYRIA3`. Transcript alignment is usage based, and external embeddings cost **100 credits per 1M tokens**. ## Request status and publishing Use `GET /v2/status` with any `request_id` returned by a V2 generation route. Status resolution accepts external request ids, normalized external ids, and internal video session ids where available. Use `GET /v2/status_detailed` or `POST /v2/status_detailed` when a video client needs preview assets before the final render finishes. Detailed status is additive: every existing `/v2/status` field remains unchanged, and video requests also include `status_detail_schema: "video_session_preview.v1"` with a normalized `session` preview payload. Use `session.previewStage` to choose the furthest playable stage, then render scene clips from `session.layers[].preview`, speech from `session.audioLayers`, background audio from `session.globalAudioLayers`, and final output metadata from `session.result` once available. When image, AI-video, lip-sync, sound-effect, or final video generation reaches a terminal failure, `/v2/status` and `/v2/status_detailed` promote the exact stored provider/model message to `generationError`, `expressGenerationError`, `error`, and `message`. Detailed status also keeps the error on the failed layer asset (`session.layers[].imageSession.generationError`, `aiVideoGenerationError`, `lipSyncVideoGenerationError`, or `soundEffectVideoGenerationError`). Clients should display this message as-is for the terminal failure. Stale errors from a layer that is actively retrying or has since completed are not treated as terminal. Branched video sessions additionally include `branching.schema: "branched_video_status.v1"` on both status responses. Use `branching.summary` for aggregate leaf-render progress, `branching.tree.choice_points` for interactive choices, and `branching.paths` for path-specific frame/video state. On detailed status, each path also includes a normalized `timeline` and `audio_timeline`; resolve their `layer_id` and `audio_layer_id` references against the shared assets in `session.layers` and `session.audioLayers`. This keeps preview responses normalized even when several paths share the same parent scenes. When all leaf renders complete, `branching.outputs.ready` becomes `true`, `branching.outputs.paths` maps each leaf `path_id` to its final URL, top-level `result_url` identifies the default path, and ordered `result_urls` contains all leaf videos. The older `branch_results` mapping remains available for compatibility. See [Render a narrative as video](/narrative/render-video#branch-rendering) for the complete response and client-switching flow. Publishing and archive routes operate on the resolved actor's accessible assets: - `POST /v2/archive` - `POST /v2/publish` CONTENT_END === DOCUMENT END ===