samsar-js 0.48.16 → 0.48.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -43,6 +43,21 @@ const video = await samsar.createVideoFromText(
43
43
  { webhookUrl: 'https://example.com/webhook' },
44
44
  );
45
45
 
46
+ // Create a text video with a generated QR outro and bottom CTA footer
47
+ await samsar.createVideoFromText({
48
+ prompt: 'A boutique hotel launch reel with cinematic room details',
49
+ image_model: 'GPTIMAGE2',
50
+ video_model: 'RUNWAYML',
51
+ duration: 20,
52
+ aspect_ratio: '9:16',
53
+ generate_outro_image: true,
54
+ cta_url: 'https://example.com/book',
55
+ cta_text_top: 'Scan to book',
56
+ cta_text_bottom: 'Opening offers',
57
+ add_footer_animation: true,
58
+ footer_metadata: [{ url: 'https://example.com/book', title: 'Book your stay' }],
59
+ });
60
+
46
61
  // Create a video from an image list
47
62
  const videoFromImages = await samsar.createVideoFromImageList(
48
63
  {
@@ -91,6 +106,31 @@ await samsar.createVideoFromImageList({
91
106
  ],
92
107
  });
93
108
 
109
+ // Update an existing outro with a new provided outro image URL
110
+ await samsar.updateVideoOutroImage(
111
+ {
112
+ videoSessionId: videoFromImages.data.session_id ?? videoFromImages.data.request_id!,
113
+ outro_image_url: 'https://cdn.example.com/outro-v2.png',
114
+ add_outro_animation: true,
115
+ add_outro_focus_area: true,
116
+ outro_focust_area: { x: 680, y: 296, width: 432, height: 432 },
117
+ },
118
+ { webhookUrl: 'https://example.com/webhook' },
119
+ );
120
+
121
+ // Update an existing outro by generating a new QR CTA outro server-side
122
+ await samsar.updateVideoOutroImage(
123
+ {
124
+ videoSessionId: videoFromImages.data.session_id ?? videoFromImages.data.request_id!,
125
+ generate_outro_image: true,
126
+ cta_url: 'https://example.com/book',
127
+ cta_text_top: 'Scan to book',
128
+ cta_text_bottom: 'Limited availability',
129
+ add_outro_animation: true,
130
+ },
131
+ { webhookUrl: 'https://example.com/webhook' },
132
+ );
133
+
94
134
  // Translate an existing video session into another language
95
135
  const translated = await samsar.translateVideo(
96
136
  {
@@ -302,6 +342,24 @@ completedSessions.data.forEach((session) => {
302
342
  console.log(session.session_id, session.langauge, session.result_url);
303
343
  });
304
344
 
345
+ // Publish, edit, or revoke a completed session in the public publication feed (free endpoints)
346
+ const publication = await samsar.publishPublication({
347
+ session_id: videoFromImages.data.session_id ?? videoFromImages.data.request_id!,
348
+ title: 'Running shoe teaser',
349
+ description: 'Launch-day vertical cut',
350
+ tags: ['launch', 'footwear'],
351
+ creator_handle: 'acme',
352
+ });
353
+ console.log(publication.data.publication?.publication_id);
354
+
355
+ await samsar.editPublication({
356
+ session_id: videoFromImages.data.session_id ?? videoFromImages.data.request_id!,
357
+ title: 'Running shoe teaser - updated',
358
+ tags: ['launch', 'footwear', 'campaign'],
359
+ });
360
+
361
+ await samsar.revokePublication(videoFromImages.data.session_id ?? videoFromImages.data.request_id!);
362
+
305
363
  // Image-specific status endpoint
306
364
  const rollupStatus = await samsar.getImageStatus(rollup.data.session_id);
307
365
 
@@ -428,9 +486,12 @@ console.log(externalLibrary.data.requests.map((request) => request.request_id));
428
486
  Video model support notes:
429
487
  - `createVideoFromText` image model keys include: `GPTIMAGE2`, `IMAGEN4`, `SEEDREAM`, `HUNYUAN`, `NANOBANANA2`.
430
488
  - `createVideoFromText` supports all express video models: `RUNWAYML`, `KLINGIMGTOVID3PRO`, `HAILUO`, `HAILUOPRO`, `SEEDANCEI2V` (Seedance 2.0), `VEO3.1I2V`, `VEO3.1I2VFAST`, `SORA2`, `SORA2PRO`.
489
+ - `createVideoFromText` accepts either a provided outro (`outro_image_url`) or server-generated QR outro (`generate_outro_image: true` with `cta_url`). It can also render bottom CTA footer QR cards with `add_footer_animation` and `footer_metadata`; one footer item applies to every generated scene, while multiple items map by scene index.
431
490
  - `createVideoFromImageList` supports `VEO3.1I2V`, `SEEDANCEI2V`, `KLING3.0`, and `RUNWAYML` via `video_model`; if omitted, it defaults to `VEO3.1I2V`. `KLINGIMGTOVID3PRO` is also accepted as a compatibility alias for `KLING3.0`. Use `aspect_ratio: '16:9'` or `'9:16'`; omitted or invalid values fall back to `16:9`.
432
491
  - `createVideoFromImageList` accepts either a provided outro (`outro_image_url`) or server-generated QR outro (`generate_outro_image: true` with `cta_url`). Do not combine the two modes in a single request.
433
492
  - `createVideoFromImageList` can render per-scene footer QR cards by setting `add_footer_animation: true` and providing one `footer_metadata` item per image scene.
493
+ - `updateVideoOutroImage` accepts either a replacement outro image URL (`outro_image_url`, `outroImageUrl`, `new_outro_image_url`) or a generated QR CTA outro (`generate_outro_image: true` with `cta_url`, or just `cta_url` when no outro image URL is supplied). Generated outro updates reuse the existing session image layers for tiling and only queue frame/video regeneration.
494
+ - `publishPublication`, `editPublication`, and `revokePublication` manage public feed publications for completed sessions through free `/publications/*` endpoints. They work with account API keys, customer sub-account API keys, and client auth tokens when the session belongs to the authenticated actor.
434
495
  - Image-list video pricing is per rendered second: `VEO3.1I2V` and `SEEDANCEI2V` are 75 credits/sec, `KLING3.0` is 50 credits/sec, and `RUNWAYML` is 25 credits/sec.
435
496
 
436
497
  Each method returns `{ data, status, headers, creditsCharged, creditsRemaining, raw }`. Non-2xx responses throw `SamsarRequestError` containing status, body, and credit headers (if present).
package/dist/index.d.ts CHANGED
@@ -50,6 +50,30 @@ export interface CreateVideoFromTextInput {
50
50
  session_id?: string;
51
51
  sessionId?: string;
52
52
  sessionID?: string;
53
+ outro_image_url?: string;
54
+ outroImageUrl?: string;
55
+ add_outro_animation?: boolean;
56
+ addOutroAnimation?: boolean;
57
+ add_outro_focus_area?: boolean;
58
+ addOutroFocusArea?: boolean;
59
+ outro_focust_area?: OutroFocusArea | null;
60
+ outro_focus_area?: OutroFocusArea | null;
61
+ outroFocustArea?: OutroFocusArea | null;
62
+ outroFocusArea?: OutroFocusArea | null;
63
+ generate_outro_image?: boolean;
64
+ generateOutroImage?: boolean;
65
+ cta_url?: string;
66
+ ctaUrl?: string;
67
+ cta_text_top?: string;
68
+ ctaTextTop?: string;
69
+ cta_text_bottom?: string;
70
+ ctaTextBottom?: string;
71
+ cta_logo?: string;
72
+ ctaLogo?: string;
73
+ add_footer_animation?: boolean;
74
+ addFooterAnimation?: boolean;
75
+ footer_metadata?: FooterMetadataItem[];
76
+ footerMetadata?: FooterMetadataItem[];
53
77
  [key: string]: unknown;
54
78
  }
55
79
  export interface CreateVideoResponse {
@@ -204,6 +228,24 @@ export interface UpdateVideoOutroImageInput {
204
228
  outroImageUrl?: string;
205
229
  new_outro_image_url?: string;
206
230
  newOutroImageUrl?: string;
231
+ add_outro_animation?: boolean;
232
+ addOutroAnimation?: boolean;
233
+ add_outro_focus_area?: boolean;
234
+ addOutroFocusArea?: boolean;
235
+ outro_focust_area?: OutroFocusArea | null;
236
+ outro_focus_area?: OutroFocusArea | null;
237
+ outroFocustArea?: OutroFocusArea | null;
238
+ outroFocusArea?: OutroFocusArea | null;
239
+ generate_outro_image?: boolean;
240
+ generateOutroImage?: boolean;
241
+ cta_url?: string;
242
+ ctaUrl?: string;
243
+ cta_text_top?: string;
244
+ ctaTextTop?: string;
245
+ cta_text_bottom?: string;
246
+ ctaTextBottom?: string;
247
+ cta_logo?: string;
248
+ ctaLogo?: string;
207
249
  [key: string]: unknown;
208
250
  }
209
251
  export interface UpdateVideoOutroImageResponse {
@@ -316,6 +358,96 @@ export interface CompletedVideoSession {
316
358
  [key: string]: unknown;
317
359
  }
318
360
  export type ListCompletedVideoSessionsResponse = CompletedVideoSession[];
361
+ export interface SessionPublicationInput {
362
+ session_id?: string;
363
+ sessionId?: string;
364
+ sessionID?: string;
365
+ video_session_id?: string;
366
+ videoSessionId?: string;
367
+ videoSessionID?: string;
368
+ id?: string;
369
+ title?: string;
370
+ description?: string;
371
+ tags?: string[] | string;
372
+ aspect_ratio?: string;
373
+ aspectRatio?: string;
374
+ creator_handle?: string;
375
+ creatorHandle?: string;
376
+ slug?: string;
377
+ image_hash?: string;
378
+ imageHash?: string;
379
+ splash_image?: string;
380
+ splashImage?: string;
381
+ image_model?: string;
382
+ imageModel?: string;
383
+ video_model?: string;
384
+ videoModel?: string;
385
+ original_prompt?: string;
386
+ originalPrompt?: string;
387
+ prompt?: string;
388
+ session_language?: string;
389
+ sessionLanguage?: string;
390
+ language?: string;
391
+ language_code?: string;
392
+ languageString?: string;
393
+ language_string?: string;
394
+ [key: string]: unknown;
395
+ }
396
+ export interface SessionPublicationSummary {
397
+ id?: string | null;
398
+ publication_id?: string | null;
399
+ session_id?: string | null;
400
+ sessionId?: string | null;
401
+ video_url?: string | null;
402
+ videoUrl?: string | null;
403
+ title?: string | null;
404
+ description?: string;
405
+ tags?: string[];
406
+ creator_handle?: string;
407
+ creatorHandle?: string;
408
+ slug?: string | null;
409
+ image_hash?: string | null;
410
+ imageHash?: string | null;
411
+ splash_image?: string | null;
412
+ splashImage?: string | null;
413
+ image_model?: string | null;
414
+ imageModel?: string | null;
415
+ video_model?: string | null;
416
+ videoModel?: string | null;
417
+ original_prompt?: string;
418
+ originalPrompt?: string;
419
+ session_language?: string | null;
420
+ sessionLanguage?: string | null;
421
+ language_string?: string | null;
422
+ languageString?: string | null;
423
+ aspect_ratio?: string | null;
424
+ aspectRatio?: string | null;
425
+ created_at?: string | null;
426
+ createdAt?: string | null;
427
+ updated_at?: string | null;
428
+ updatedAt?: string | null;
429
+ [key: string]: unknown;
430
+ }
431
+ export interface SessionPublicationSessionSummary {
432
+ id?: string | null;
433
+ session_id?: string | null;
434
+ is_published?: boolean;
435
+ published_publication_id?: string | null;
436
+ published_video_url?: string | null;
437
+ published_at?: string | null;
438
+ [key: string]: unknown;
439
+ }
440
+ export interface SessionPublicationResponse {
441
+ created?: boolean;
442
+ revoked?: boolean;
443
+ publication_id?: string | null;
444
+ publication?: SessionPublicationSummary | null;
445
+ session?: SessionPublicationSessionSummary | null;
446
+ sessionId?: string;
447
+ session_id?: string;
448
+ ispublishedVideo?: boolean;
449
+ [key: string]: unknown;
450
+ }
319
451
  export interface SupportedTextToVideoModelsResponse {
320
452
  IMAGE_MODELS: SupportedTextToVideoModelOption[];
321
453
  VIDEO_MODELS: SupportedTextToVideoModelOption[];
@@ -1471,6 +1603,33 @@ export declare class SamsarClient {
1471
1603
  listCompletedVideoSessions(options?: SamsarRequestOptions & {
1472
1604
  limit?: number;
1473
1605
  }): Promise<SamsarResult<ListCompletedVideoSessionsResponse>>;
1606
+ /**
1607
+ * Publish a completed Samsar video session to the public publication feed.
1608
+ * This is a free endpoint. The session must belong to the authenticated API key/auth token.
1609
+ */
1610
+ publishPublication(input: string | SessionPublicationInput, options?: SamsarRequestOptions): Promise<SamsarResult<SessionPublicationResponse>>;
1611
+ /**
1612
+ * Alias for publishPublication, named around the underlying VideoSession resource.
1613
+ */
1614
+ publishSessionPublication(input: string | SessionPublicationInput, options?: SamsarRequestOptions): Promise<SamsarResult<SessionPublicationResponse>>;
1615
+ /**
1616
+ * Edit an existing publication for a Samsar video session.
1617
+ * Omitted fields keep their current publication values.
1618
+ */
1619
+ editPublication(input: SessionPublicationInput, options?: SamsarRequestOptions): Promise<SamsarResult<SessionPublicationResponse>>;
1620
+ /**
1621
+ * Alias for editPublication, named around the underlying VideoSession resource.
1622
+ */
1623
+ editSessionPublication(input: SessionPublicationInput, options?: SamsarRequestOptions): Promise<SamsarResult<SessionPublicationResponse>>;
1624
+ /**
1625
+ * Revoke a publication for a Samsar video session.
1626
+ * This deletes the publication record and clears published fields on the session.
1627
+ */
1628
+ revokePublication(input: string | SessionPublicationInput, options?: SamsarRequestOptions): Promise<SamsarResult<SessionPublicationResponse>>;
1629
+ /**
1630
+ * Alias for revokePublication, named around the underlying VideoSession resource.
1631
+ */
1632
+ revokeSessionPublication(input: string | SessionPublicationInput, options?: SamsarRequestOptions): Promise<SamsarResult<SessionPublicationResponse>>;
1474
1633
  /**
1475
1634
  * Fetch the supported image/video model keys for the text_to_video route.
1476
1635
  * Maps to GET /video/supported_models.
package/dist/index.js CHANGED
@@ -34,11 +34,53 @@ function resolveAliasedInputValue(raw, aliases, canonicalName) {
34
34
  }
35
35
  return hasResolved ? resolved : undefined;
36
36
  }
37
- function assertOptionalBoolean(value, fieldName) {
37
+ function assertOptionalBoolean(value, fieldName, context = 'createVideoFromImageList') {
38
38
  if (value !== undefined && typeof value !== 'boolean') {
39
- throw new Error(`${fieldName} must be a boolean for createVideoFromImageList`);
39
+ throw new Error(`${fieldName} must be a boolean for ${context}`);
40
40
  }
41
41
  }
42
+ function normalizeCreateVideoFromTextInput(input) {
43
+ const raw = input;
44
+ const normalized = { ...input };
45
+ const aliases = [
46
+ ['session_id', ['session_id', 'sessionId', 'sessionID']],
47
+ ['aspect_ratio', ['aspect_ratio', 'aspectRatio']],
48
+ ['outro_image_url', ['outro_image_url', 'outroImageUrl']],
49
+ ['add_outro_animation', ['add_outro_animation', 'addOutroAnimation']],
50
+ ['add_outro_focus_area', ['add_outro_focus_area', 'addOutroFocusArea']],
51
+ ['outro_focust_area', ['outro_focust_area', 'outro_focus_area', 'outroFocustArea', 'outroFocusArea']],
52
+ ['generate_outro_image', ['generate_outro_image', 'generateOutroImage']],
53
+ ['cta_url', ['cta_url', 'ctaUrl']],
54
+ ['cta_text_top', ['cta_text_top', 'ctaTextTop']],
55
+ ['cta_text_bottom', ['cta_text_bottom', 'ctaTextBottom']],
56
+ ['cta_logo', ['cta_logo', 'ctaLogo']],
57
+ ['add_footer_animation', ['add_footer_animation', 'addFooterAnimation']],
58
+ ['footer_metadata', ['footer_metadata', 'footerMetadata']],
59
+ ['enable_subtitles', ['enable_subtitles', 'enableSubtitles']],
60
+ ['font_key', ['font_key', 'fontKey']],
61
+ ];
62
+ for (const [canonicalName, aliasList] of aliases) {
63
+ const value = resolveAliasedInputValue(raw, aliasList, canonicalName);
64
+ if (value !== undefined) {
65
+ normalized[canonicalName] = value;
66
+ }
67
+ }
68
+ assertOptionalBoolean(normalized.enable_subtitles, 'enable_subtitles', 'createVideoFromText');
69
+ assertOptionalBoolean(normalized.add_outro_animation, 'add_outro_animation', 'createVideoFromText');
70
+ assertOptionalBoolean(normalized.add_outro_focus_area, 'add_outro_focus_area', 'createVideoFromText');
71
+ assertOptionalBoolean(normalized.generate_outro_image, 'generate_outro_image', 'createVideoFromText');
72
+ assertOptionalBoolean(normalized.add_footer_animation, 'add_footer_animation', 'createVideoFromText');
73
+ if (normalized.generate_outro_image === true) {
74
+ const ctaUrl = typeof normalized.cta_url === 'string' ? normalized.cta_url.trim() : '';
75
+ if (!ctaUrl) {
76
+ throw new Error('cta_url is required when generate_outro_image is true for createVideoFromText');
77
+ }
78
+ }
79
+ else if (normalized.add_outro_focus_area === true && normalized.add_outro_animation !== true) {
80
+ throw new Error('add_outro_focus_area requires add_outro_animation to be true for createVideoFromText');
81
+ }
82
+ return normalized;
83
+ }
42
84
  function normalizeCreateVideoFromImageListInput(input) {
43
85
  const raw = input;
44
86
  if (!Array.isArray(input.image_urls) || input.image_urls.length === 0) {
@@ -57,6 +99,8 @@ function normalizeCreateVideoFromImageListInput(input) {
57
99
  ['cta_text_top', ['cta_text_top', 'ctaTextTop']],
58
100
  ['cta_text_bottom', ['cta_text_bottom', 'ctaTextBottom']],
59
101
  ['cta_logo', ['cta_logo', 'ctaLogo']],
102
+ ['add_footer_animation', ['add_footer_animation', 'addFooterAnimation']],
103
+ ['footer_metadata', ['footer_metadata', 'footerMetadata']],
60
104
  ['enable_subtitles', ['enable_subtitles', 'enableSubtitles']],
61
105
  ['font_key', ['font_key', 'fontKey']],
62
106
  ];
@@ -70,6 +114,7 @@ function normalizeCreateVideoFromImageListInput(input) {
70
114
  assertOptionalBoolean(normalized.add_outro_animation, 'add_outro_animation');
71
115
  assertOptionalBoolean(normalized.add_outro_focus_area, 'add_outro_focus_area');
72
116
  assertOptionalBoolean(normalized.generate_outro_image, 'generate_outro_image');
117
+ assertOptionalBoolean(normalized.add_footer_animation, 'add_footer_animation');
73
118
  if (normalized.generate_outro_image === true) {
74
119
  const ctaUrl = typeof normalized.cta_url === 'string' ? normalized.cta_url.trim() : '';
75
120
  if (!ctaUrl) {
@@ -81,6 +126,58 @@ function normalizeCreateVideoFromImageListInput(input) {
81
126
  }
82
127
  return normalized;
83
128
  }
129
+ function normalizeSessionPublicationInput(input, context) {
130
+ if (typeof input !== 'string' && (!input || typeof input !== 'object' || Array.isArray(input))) {
131
+ throw new Error(`input must be a session id or object for ${context}`);
132
+ }
133
+ const raw = typeof input === 'string' ? { session_id: input } : { ...input };
134
+ const sessionId = resolveAliasedInputValue(raw, [
135
+ 'session_id',
136
+ 'sessionId',
137
+ 'sessionID',
138
+ 'video_session_id',
139
+ 'videoSessionId',
140
+ 'videoSessionID',
141
+ 'id',
142
+ ], 'session_id');
143
+ const normalizedSessionId = typeof sessionId === 'string' ? sessionId.trim() : '';
144
+ if (!normalizedSessionId) {
145
+ throw new Error(`session_id is required for ${context}`);
146
+ }
147
+ const normalized = {
148
+ ...raw,
149
+ session_id: normalizedSessionId,
150
+ };
151
+ const aliases = [
152
+ ['aspect_ratio', ['aspect_ratio', 'aspectRatio']],
153
+ ['creator_handle', ['creator_handle', 'creatorHandle']],
154
+ ['image_hash', ['image_hash', 'imageHash']],
155
+ ['splash_image', ['splash_image', 'splashImage']],
156
+ ['image_model', ['image_model', 'imageModel']],
157
+ ['video_model', ['video_model', 'videoModel']],
158
+ ['original_prompt', ['original_prompt', 'originalPrompt', 'prompt']],
159
+ ['session_language', ['session_language', 'sessionLanguage', 'language', 'language_code']],
160
+ ['language_string', ['language_string', 'languageString']],
161
+ ];
162
+ for (const [canonicalName, aliasList] of aliases) {
163
+ const value = resolveAliasedInputValue(raw, aliasList, canonicalName);
164
+ if (value !== undefined) {
165
+ normalized[canonicalName] = value;
166
+ }
167
+ }
168
+ if (normalized.tags !== undefined &&
169
+ !Array.isArray(normalized.tags) &&
170
+ typeof normalized.tags !== 'string') {
171
+ throw new Error(`tags must be a string or string array for ${context}`);
172
+ }
173
+ if (Array.isArray(normalized.tags)) {
174
+ normalized.tags = normalized.tags
175
+ .filter((tag) => typeof tag === 'string')
176
+ .map((tag) => tag.trim())
177
+ .filter(Boolean);
178
+ }
179
+ return normalized;
180
+ }
84
181
  export class SamsarClient {
85
182
  constructor(options) {
86
183
  if (!options?.apiKey) {
@@ -100,8 +197,9 @@ export class SamsarClient {
100
197
  * Create a new video generation job from text.
101
198
  */
102
199
  async createVideoFromText(input, options) {
200
+ const normalizedInput = normalizeCreateVideoFromTextInput(input);
103
201
  const body = {
104
- input,
202
+ input: normalizedInput,
105
203
  webhookUrl: options?.webhookUrl,
106
204
  };
107
205
  return this.post('video/create', body, options);
@@ -140,9 +238,10 @@ export class SamsarClient {
140
238
  * Create a text-to-video request attributed to an external user while billing against the shared API key.
141
239
  */
142
240
  async createExternalVideoFromText(externalUser, input, options) {
241
+ const normalizedInput = normalizeCreateVideoFromTextInput(input);
143
242
  const body = {
144
243
  external_user: normalizeExternalUserIdentity(externalUser),
145
- input,
244
+ input: normalizedInput,
146
245
  webhookUrl: options?.webhookUrl,
147
246
  };
148
247
  return this.post('external_users/text_to_video', body, options);
@@ -477,17 +576,75 @@ export class SamsarClient {
477
576
  raw.outroImageUrl ??
478
577
  raw.new_outro_image_url ??
479
578
  raw.newOutroImageUrl;
579
+ const rawGenerateOutroImage = raw.generate_outro_image ??
580
+ raw.generateOutroImage;
581
+ const rawAddOutroAnimation = raw.add_outro_animation ??
582
+ raw.addOutroAnimation;
583
+ const rawAddOutroFocusArea = raw.add_outro_focus_area ??
584
+ raw.addOutroFocusArea;
585
+ const rawOutroFocusArea = raw.outro_focust_area ??
586
+ raw.outro_focus_area ??
587
+ raw.outroFocustArea ??
588
+ raw.outroFocusArea;
589
+ const ctaUrl = raw.cta_url ??
590
+ raw.ctaUrl;
591
+ const ctaTextTop = raw.cta_text_top ??
592
+ raw.ctaTextTop;
593
+ const ctaTextBottom = raw.cta_text_bottom ??
594
+ raw.ctaTextBottom;
595
+ const ctaLogo = raw.cta_logo ??
596
+ raw.ctaLogo;
597
+ const generateOutroImage = rawGenerateOutroImage === true ||
598
+ (rawGenerateOutroImage === undefined && !outroImageUrl && Boolean(ctaUrl));
480
599
  if (!videoSessionId) {
481
600
  throw new Error('videoSessionId is required for updateVideoOutroImage');
482
601
  }
483
- if (!outroImageUrl) {
484
- throw new Error('outro_image_url is required for updateVideoOutroImage');
602
+ if (rawGenerateOutroImage !== undefined && typeof rawGenerateOutroImage !== 'boolean') {
603
+ throw new Error('generate_outro_image must be a boolean for updateVideoOutroImage');
604
+ }
605
+ if (rawAddOutroAnimation !== undefined && typeof rawAddOutroAnimation !== 'boolean') {
606
+ throw new Error('add_outro_animation must be a boolean for updateVideoOutroImage');
607
+ }
608
+ if (rawAddOutroFocusArea !== undefined && typeof rawAddOutroFocusArea !== 'boolean') {
609
+ throw new Error('add_outro_focus_area must be a boolean for updateVideoOutroImage');
610
+ }
611
+ if (generateOutroImage && outroImageUrl) {
612
+ throw new Error('Use either generate_outro_image with cta_url or outro_image_url for updateVideoOutroImage');
613
+ }
614
+ if (!generateOutroImage && !outroImageUrl) {
615
+ throw new Error('outro_image_url is required for updateVideoOutroImage unless generate_outro_image is true');
616
+ }
617
+ if (generateOutroImage) {
618
+ if (!ctaUrl || !String(ctaUrl).trim()) {
619
+ throw new Error('cta_url is required when generate_outro_image is true for updateVideoOutroImage');
620
+ }
621
+ }
622
+ else if (rawAddOutroFocusArea === true && rawAddOutroAnimation !== true) {
623
+ throw new Error('add_outro_focus_area requires add_outro_animation to be true for updateVideoOutroImage');
624
+ }
625
+ if (!generateOutroImage && rawAddOutroFocusArea === true) {
626
+ if (!rawOutroFocusArea || typeof rawOutroFocusArea !== 'object' || Array.isArray(rawOutroFocusArea)) {
627
+ throw new Error('outro_focust_area must be an object with x, y, width, height for updateVideoOutroImage');
628
+ }
629
+ const { x, y, width, height } = rawOutroFocusArea;
630
+ const isInvalid = [x, y, width, height].some((value) => typeof value !== 'number' || !Number.isFinite(value));
631
+ if (isInvalid) {
632
+ throw new Error('outro_focust_area x, y, width, height must be valid numbers for updateVideoOutroImage');
633
+ }
485
634
  }
486
635
  const body = {
487
636
  input: {
488
637
  ...input,
489
638
  videoSessionId: String(videoSessionId),
490
- outro_image_url: String(outroImageUrl),
639
+ ...(outroImageUrl ? { outro_image_url: String(outroImageUrl) } : {}),
640
+ generate_outro_image: generateOutroImage,
641
+ ...(generateOutroImage ? { cta_url: String(ctaUrl).trim() } : {}),
642
+ ...(ctaTextTop ? { cta_text_top: String(ctaTextTop) } : {}),
643
+ ...(ctaTextBottom ? { cta_text_bottom: String(ctaTextBottom) } : {}),
644
+ ...(ctaLogo ? { cta_logo: String(ctaLogo) } : {}),
645
+ ...(rawAddOutroAnimation !== undefined ? { add_outro_animation: rawAddOutroAnimation === true } : {}),
646
+ ...(rawAddOutroFocusArea !== undefined ? { add_outro_focus_area: rawAddOutroFocusArea === true } : {}),
647
+ ...(rawOutroFocusArea !== undefined ? { outro_focust_area: rawOutroFocusArea } : {}),
491
648
  },
492
649
  webhookUrl: options?.webhookUrl,
493
650
  };
@@ -669,6 +826,48 @@ export class SamsarClient {
669
826
  data: normalizedData,
670
827
  };
671
828
  }
829
+ /**
830
+ * Publish a completed Samsar video session to the public publication feed.
831
+ * This is a free endpoint. The session must belong to the authenticated API key/auth token.
832
+ */
833
+ async publishPublication(input, options) {
834
+ const payload = normalizeSessionPublicationInput(input, 'publishPublication');
835
+ return this.post('publications/publish', payload, options);
836
+ }
837
+ /**
838
+ * Alias for publishPublication, named around the underlying VideoSession resource.
839
+ */
840
+ async publishSessionPublication(input, options) {
841
+ return this.publishPublication(input, options);
842
+ }
843
+ /**
844
+ * Edit an existing publication for a Samsar video session.
845
+ * Omitted fields keep their current publication values.
846
+ */
847
+ async editPublication(input, options) {
848
+ const payload = normalizeSessionPublicationInput(input, 'editPublication');
849
+ return this.post('publications/edit', payload, options);
850
+ }
851
+ /**
852
+ * Alias for editPublication, named around the underlying VideoSession resource.
853
+ */
854
+ async editSessionPublication(input, options) {
855
+ return this.editPublication(input, options);
856
+ }
857
+ /**
858
+ * Revoke a publication for a Samsar video session.
859
+ * This deletes the publication record and clears published fields on the session.
860
+ */
861
+ async revokePublication(input, options) {
862
+ const payload = normalizeSessionPublicationInput(input, 'revokePublication');
863
+ return this.post('publications/revoke', payload, options);
864
+ }
865
+ /**
866
+ * Alias for revokePublication, named around the underlying VideoSession resource.
867
+ */
868
+ async revokeSessionPublication(input, options) {
869
+ return this.revokePublication(input, options);
870
+ }
672
871
  /**
673
872
  * Fetch the supported image/video model keys for the text_to_video route.
674
873
  * Maps to GET /video/supported_models.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "samsar-js",
3
- "version": "0.48.16",
3
+ "version": "0.48.18",
4
4
  "description": "TypeScript client for the Samsar Processor API routes.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",