Skip to main content

Create a branching narrative

POST /v2/external/narrative/create_branching

Expand a completed 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 when you want one final video per leaf path.

Request body

{
"narrative_request_id": "687b3df76a8d56c94ab32100",
"num_levels": 1
}
FieldTypeRequiredDescription
narrative_request_idstringYesID of the completed singular NarrativeRequest to branch. It must belong to the authenticated user.
num_levelsintegerYesNumber 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

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:

import SamsarClient from 'samsar-js';

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:

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:

{
"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

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:

StatusMeaning
PENDINGThe request is persisted and waiting for the user's narrative-processing slot.
PROCESSINGDivergence planning, branch generation, validation, or usage settlement is in progress.
COMPLETEDTerminal success. The complete tree and branching metadata are available.
FAILEDTerminal 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:

{
"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

{
"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:

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:

{
"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.