samsar-js 0.48.18 → 0.48.20

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
@@ -423,6 +423,20 @@ const externalRender = await platform.createExternalVideoFromText(externalUser,
423
423
  enable_subtitles: true,
424
424
  });
425
425
 
426
+ // Update an external user's existing outro by generating a new QR CTA outro
427
+ const externalOutroUpdate = await platform.updateExternalVideoOutroImage(externalUser, {
428
+ request_id: externalRender.data.request_id,
429
+ generate_outro_image: true,
430
+ cta_url: 'https://example.com/shop',
431
+ cta_text_top: 'Scan to shop',
432
+ cta_text_bottom: 'Limited drop',
433
+ add_outro_animation: true,
434
+ });
435
+ console.log(externalOutroUpdate.data.request_id);
436
+
437
+ // Repeated video routes accept the returned extreq_ id or the normalized external id.
438
+ // The API resolves ownership through external request mappings and GlobalSession records.
439
+
426
440
  // Run an assistant completion against one of that external user's sessions.
427
441
  // Credits are deducted from the external user, while the owning Samsar account model config is used internally.
428
442
  const externalAssistant = await platform.createExternalAssistantCompletion(
@@ -491,9 +505,57 @@ Video model support notes:
491
505
  - `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.
492
506
  - `createVideoFromImageList` can render per-scene footer QR cards by setting `add_footer_animation: true` and providing one `footer_metadata` item per image scene.
493
507
  - `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.
508
+ - Main video methods and external-user methods accept the same generated outro and footer parameters. The API can resolve either internal session ids or external `extreq_...` ids on repeated video routes, so client code can keep using `translateVideo`, `joinVideos`, `addVideoOutroImage`, and `updateVideoOutroImage`; the explicit external variants are available when you want to call `/external_users/*` directly. Do not strip the `extreq_` prefix.
494
509
  - `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.
495
510
  - 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.
496
511
 
512
+ Upcoming `/v2` omni route adapters:
513
+ - `/v2` is additive; `/v1` is not deprecated.
514
+ - `createV2VideoFromText`, `createV2VideoFromImageList`, `updateV2VideoOutroImage`, `addV2VideoOutroImage`, `getV2Status`, `getV2Credits`, `listV2Requests`, and `createV2Session` call the new omni route surface.
515
+ - Programmatic user billing helpers include `createV2UserRechargeCredits`, `refreshV2UserToken`, `getV2UserCredits`, `getV2UserUsageLogs`, and `getV2UserPaymentStatus`.
516
+ - Omit `externalUser` for internal account billing, pass `externalUser` to scope an external user with the account API key, or authenticate the client directly with an external-user auth token/API key.
517
+
518
+ ```ts
519
+ const v2Video = await platform.createV2VideoFromImageList({
520
+ image_urls: ['https://cdn.example.com/a.png', 'https://cdn.example.com/b.png'],
521
+ video_model: 'RUNWAYML',
522
+ generate_outro_image: true,
523
+ cta_url: 'https://example.com/book',
524
+ cta_text_top: 'Scan to book',
525
+ });
526
+
527
+ const v2ExternalVideo = await platform.createV2VideoFromImageList(
528
+ {
529
+ image_urls: ['https://cdn.example.com/a.png', 'https://cdn.example.com/b.png'],
530
+ video_model: 'KLING3.0',
531
+ },
532
+ { externalUser },
533
+ );
534
+
535
+ const v2Status = await platform.getV2Status(v2Video.data.request_id!);
536
+ console.log(v2Status.data.status);
537
+ ```
538
+
539
+ Programmatic user recharge and OAuth-style refresh token rotation:
540
+
541
+ ```ts
542
+ const publicClient = new SamsarClient({});
543
+
544
+ const checkout = await publicClient.createV2UserRechargeCredits({
545
+ amount: 25,
546
+ email: 'customer@example.com',
547
+ redirect_url: 'https://example.com/samsar/callback',
548
+ });
549
+
550
+ // After the Stripe webhook calls your redirect_url with authToken, refreshToken, and expiryDate:
551
+ const userClient = new SamsarClient({ apiKey: authToken });
552
+ const refreshed = await userClient.refreshV2UserToken(refreshToken);
553
+
554
+ localStorage.setItem('authToken', refreshed.data.authToken);
555
+ localStorage.setItem('refreshToken', refreshed.data.refreshToken);
556
+ localStorage.setItem('expiryDate', refreshed.data.expiryDate);
557
+ ```
558
+
497
559
  Each method returns `{ data, status, headers, creditsCharged, creditsRemaining, raw }`. Non-2xx responses throw `SamsarRequestError` containing status, body, and credit headers (if present).
498
560
 
499
561
  ## Billing notes
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Respo
2
2
  type QueryValue = string | number | boolean | null | undefined;
3
3
  type QueryParams = Record<string, QueryValue>;
4
4
  export interface SamsarClientOptions {
5
- apiKey: string;
5
+ apiKey?: string;
6
6
  baseUrl?: string;
7
7
  timeoutMs?: number;
8
8
  fetch?: FetchLike;
@@ -224,6 +224,12 @@ export interface UpdateVideoOutroImageInput {
224
224
  sessionID?: string;
225
225
  request_id?: string;
226
226
  requestId?: string;
227
+ source_request_id?: string;
228
+ sourceRequestId?: string;
229
+ external_request_id?: string;
230
+ externalRequestId?: string;
231
+ external_session_id?: string;
232
+ externalSessionId?: string;
227
233
  outro_image_url?: string;
228
234
  outroImageUrl?: string;
229
235
  new_outro_image_url?: string;
@@ -265,6 +271,12 @@ export interface AddVideoOutroImageInput {
265
271
  sessionID?: string;
266
272
  request_id?: string;
267
273
  requestId?: string;
274
+ source_request_id?: string;
275
+ sourceRequestId?: string;
276
+ external_request_id?: string;
277
+ externalRequestId?: string;
278
+ external_session_id?: string;
279
+ externalSessionId?: string;
268
280
  outro_image_url?: string;
269
281
  outroImageUrl?: string;
270
282
  new_outro_image_url?: string;
@@ -1294,6 +1306,89 @@ export interface ExternalRequestsListResponse {
1294
1306
  externalUser?: ExternalUserSummary | null;
1295
1307
  [key: string]: unknown;
1296
1308
  }
1309
+ export interface V2RequestOptions extends SamsarRequestOptions {
1310
+ externalUser?: ExternalUserIdentity | null;
1311
+ webhookUrl?: string;
1312
+ }
1313
+ export interface V2SessionResponse {
1314
+ account_type?: 'internal' | 'external' | string;
1315
+ auth_type?: string;
1316
+ user?: {
1317
+ id?: string;
1318
+ email?: string | null;
1319
+ displayName?: string | null;
1320
+ username?: string | null;
1321
+ pfpUrl?: string | null;
1322
+ preferredLanguage?: string | null;
1323
+ [key: string]: unknown;
1324
+ };
1325
+ external_api_key?: string | null;
1326
+ external_user?: ExternalUserSummary | null;
1327
+ externalUser?: ExternalUserSummary | null;
1328
+ remainingCredits?: number | null;
1329
+ [key: string]: unknown;
1330
+ }
1331
+ export interface V2RequestsListResponse {
1332
+ requests: Array<ExternalRequestSummary | Record<string, unknown>>;
1333
+ external_user?: ExternalUserSummary | null;
1334
+ externalUser?: ExternalUserSummary | null;
1335
+ [key: string]: unknown;
1336
+ }
1337
+ export interface V2UserRechargeCreditsRequest {
1338
+ amount: number;
1339
+ email: string;
1340
+ redirect_url?: string;
1341
+ redirectUrl?: string;
1342
+ [key: string]: unknown;
1343
+ }
1344
+ export interface V2UserRechargeCreditsResponse {
1345
+ url: string;
1346
+ checkoutSessionId?: string | null;
1347
+ paymentStatusEndpoint?: string;
1348
+ amount: number;
1349
+ amountCents: number;
1350
+ credits: number;
1351
+ currency: string;
1352
+ redirectUrl?: string;
1353
+ [key: string]: unknown;
1354
+ }
1355
+ export interface V2UserTokenRefreshRequest {
1356
+ refreshToken?: string;
1357
+ refresh_token?: string;
1358
+ }
1359
+ export interface V2UserTokenResponse {
1360
+ tokenType?: 'Bearer' | string;
1361
+ authToken: string;
1362
+ refreshToken: string;
1363
+ expiryDate: string;
1364
+ expiresInSeconds?: number;
1365
+ refreshTokenExpiresAt?: string;
1366
+ [key: string]: unknown;
1367
+ }
1368
+ export interface UsageLogItem {
1369
+ id?: string;
1370
+ source?: string;
1371
+ credits?: number;
1372
+ balanceAfter?: number | null;
1373
+ metadata?: Record<string, unknown>;
1374
+ direction?: string;
1375
+ createdAt?: string;
1376
+ updatedAt?: string;
1377
+ [key: string]: unknown;
1378
+ }
1379
+ export interface UsageLogsResponse {
1380
+ items: UsageLogItem[];
1381
+ pagination?: {
1382
+ page?: number;
1383
+ pageSize?: number;
1384
+ totalItems?: number;
1385
+ totalPages?: number;
1386
+ hasNextPage?: boolean;
1387
+ hasPreviousPage?: boolean;
1388
+ [key: string]: unknown;
1389
+ };
1390
+ [key: string]: unknown;
1391
+ }
1297
1392
  export interface ExternalArchiveResponse {
1298
1393
  request?: ExternalRequestSummary | null;
1299
1394
  external_user?: ExternalUserSummary | null;
@@ -1476,13 +1571,49 @@ export declare class SamsarRequestError extends Error {
1476
1571
  constructor(message: string, init: SamsarErrorInit);
1477
1572
  }
1478
1573
  export declare class SamsarClient {
1479
- private readonly apiKey;
1574
+ private readonly apiKey?;
1480
1575
  private readonly baseUrl;
1481
1576
  private readonly timeoutMs;
1482
1577
  private readonly fetchFn;
1483
1578
  private readonly defaultHeaders;
1484
1579
  private readonly externalUserApiKey?;
1485
1580
  constructor(options: SamsarClientOptions);
1581
+ /**
1582
+ * Low-level POST adapter for the upcoming /v2 omni routes.
1583
+ * Pass options.externalUser to include an external_user payload; omit it for internal-account calls
1584
+ * or when authenticating directly with an external-user auth token/API key.
1585
+ */
1586
+ postV2<T = Record<string, unknown>>(path: string, payload?: Record<string, unknown>, options?: V2RequestOptions): Promise<SamsarResult<T>>;
1587
+ /**
1588
+ * Low-level GET adapter for the upcoming /v2 omni routes.
1589
+ */
1590
+ getV2<T = Record<string, unknown>>(path: string, options?: V2RequestOptions): Promise<SamsarResult<T>>;
1591
+ createV2Session(options?: V2RequestOptions): Promise<SamsarResult<V2SessionResponse>>;
1592
+ getV2Credits(options?: V2RequestOptions): Promise<SamsarResult<CreditsBalanceResponse | ExternalCreditsBalanceResponse>>;
1593
+ getV2UserCredits(options?: V2RequestOptions): Promise<SamsarResult<CreditsBalanceResponse>>;
1594
+ getV2UserUsageLogs(options?: V2RequestOptions & {
1595
+ page?: number;
1596
+ pageSize?: number;
1597
+ limit?: number;
1598
+ }): Promise<SamsarResult<UsageLogsResponse>>;
1599
+ listV2Requests(options?: V2RequestOptions): Promise<SamsarResult<V2RequestsListResponse>>;
1600
+ createV2UserRechargeCredits(payload: V2UserRechargeCreditsRequest, options?: V2RequestOptions): Promise<SamsarResult<V2UserRechargeCreditsResponse>>;
1601
+ refreshV2UserToken(payload: string | V2UserTokenRefreshRequest, options?: V2RequestOptions): Promise<SamsarResult<V2UserTokenResponse>>;
1602
+ refreshV2UserAuthToken(payload: string | V2UserTokenRefreshRequest, options?: V2RequestOptions): Promise<SamsarResult<V2UserTokenResponse>>;
1603
+ getV2UserPaymentStatus(payload: PaymentStatusRequest, options?: V2RequestOptions): Promise<SamsarResult<PaymentStatusResponse>>;
1604
+ createV2LoginToken(options?: V2RequestOptions & {
1605
+ redirect?: string;
1606
+ }): Promise<SamsarResult<CreateLoginTokenResponse | ExternalCreateLoginTokenResponse>>;
1607
+ createV2VideoFromText(input: CreateVideoFromTextInput, options?: V2RequestOptions): Promise<SamsarResult<CreateVideoResponse | ExternalRequestResponse>>;
1608
+ createV2VideoFromImageList(input: CreateVideoFromImageListInput, options?: V2RequestOptions): Promise<SamsarResult<CreateVideoFromImageListResponse | ExternalRequestResponse>>;
1609
+ uploadV2ImageData(imageData: string[], options?: V2RequestOptions): Promise<SamsarResult<{
1610
+ image_urls: string[];
1611
+ }>>;
1612
+ updateV2VideoOutroImage(input: UpdateVideoOutroImageInput, options?: V2RequestOptions): Promise<SamsarResult<UpdateVideoOutroImageResponse | ExternalRequestResponse>>;
1613
+ addV2VideoOutroImage(input: AddVideoOutroImageInput, options?: V2RequestOptions): Promise<SamsarResult<AddVideoOutroImageResponse | ExternalRequestResponse>>;
1614
+ getV2Status(requestId: string, options?: V2RequestOptions & {
1615
+ queryParams?: QueryParams;
1616
+ }): Promise<SamsarResult<GlobalStatusResponse | ExternalStatusResponse>>;
1486
1617
  /**
1487
1618
  * Create a new video generation job from text.
1488
1619
  */
@@ -1584,6 +1715,13 @@ export declare class SamsarClient {
1584
1715
  updateVideoOutroImage(input: UpdateVideoOutroImageInput, options?: {
1585
1716
  webhookUrl?: string;
1586
1717
  } & SamsarRequestOptions): Promise<SamsarResult<UpdateVideoOutroImageResponse>>;
1718
+ /**
1719
+ * Update the outro image for an external-user video request by resolving the external request id
1720
+ * through /external_users/update_outro_image.
1721
+ */
1722
+ updateExternalVideoOutroImage(externalUser: ExternalUserIdentity, input: UpdateVideoOutroImageInput, options?: {
1723
+ webhookUrl?: string;
1724
+ } & SamsarRequestOptions): Promise<SamsarResult<ExternalRequestResponse>>;
1587
1725
  /**
1588
1726
  * Add or replace the outro image for an existing video session by cloning it into a new session and
1589
1727
  * re-running frame/video generation.
@@ -1591,6 +1729,12 @@ export declare class SamsarClient {
1591
1729
  addVideoOutroImage(input: AddVideoOutroImageInput, options?: {
1592
1730
  webhookUrl?: string;
1593
1731
  } & SamsarRequestOptions): Promise<SamsarResult<AddVideoOutroImageResponse>>;
1732
+ /**
1733
+ * Add or replace an outro image for an external-user video request by resolving the external request id.
1734
+ */
1735
+ addExternalVideoOutroImage(externalUser: ExternalUserIdentity, input: AddVideoOutroImageInput, options?: {
1736
+ webhookUrl?: string;
1737
+ } & SamsarRequestOptions): Promise<SamsarResult<ExternalRequestResponse>>;
1594
1738
  /**
1595
1739
  * Fetch the latest available render URL for a given video session id.
1596
1740
  * Maps to GET /video/fetch_latest_version?session_id={sessionId}.
@@ -1826,5 +1970,7 @@ export declare class SamsarClient {
1826
1970
  private buildHeaders;
1827
1971
  private buildUrl;
1828
1972
  private buildRootUrl;
1973
+ private withV2ExternalUser;
1974
+ private buildV2Url;
1829
1975
  }
1830
1976
  export default SamsarClient;
package/dist/index.js CHANGED
@@ -126,6 +126,96 @@ function normalizeCreateVideoFromImageListInput(input) {
126
126
  }
127
127
  return normalized;
128
128
  }
129
+ function normalizeUpdateVideoOutroImageInput(input, context = 'updateVideoOutroImage') {
130
+ const raw = input;
131
+ const videoSessionId = raw.videoSessionId ??
132
+ raw.video_session_id ??
133
+ raw.videoSessionID ??
134
+ raw.session_id ??
135
+ raw.sessionId ??
136
+ raw.sessionID ??
137
+ raw.request_id ??
138
+ raw.requestId ??
139
+ raw.source_request_id ??
140
+ raw.sourceRequestId ??
141
+ raw.external_request_id ??
142
+ raw.externalRequestId ??
143
+ raw.external_session_id ??
144
+ raw.externalSessionId;
145
+ const outroImageUrl = raw.outro_image_url ??
146
+ raw.outroImageUrl ??
147
+ raw.new_outro_image_url ??
148
+ raw.newOutroImageUrl;
149
+ const rawGenerateOutroImage = raw.generate_outro_image ??
150
+ raw.generateOutroImage;
151
+ const rawAddOutroAnimation = raw.add_outro_animation ??
152
+ raw.addOutroAnimation;
153
+ const rawAddOutroFocusArea = raw.add_outro_focus_area ??
154
+ raw.addOutroFocusArea;
155
+ const rawOutroFocusArea = raw.outro_focust_area ??
156
+ raw.outro_focus_area ??
157
+ raw.outroFocustArea ??
158
+ raw.outroFocusArea;
159
+ const ctaUrl = raw.cta_url ??
160
+ raw.ctaUrl;
161
+ const ctaTextTop = raw.cta_text_top ??
162
+ raw.ctaTextTop;
163
+ const ctaTextBottom = raw.cta_text_bottom ??
164
+ raw.ctaTextBottom;
165
+ const ctaLogo = raw.cta_logo ??
166
+ raw.ctaLogo;
167
+ const generateOutroImage = rawGenerateOutroImage === true ||
168
+ (rawGenerateOutroImage === undefined && !outroImageUrl && Boolean(ctaUrl));
169
+ if (!videoSessionId) {
170
+ throw new Error(`videoSessionId is required for ${context}`);
171
+ }
172
+ if (rawGenerateOutroImage !== undefined && typeof rawGenerateOutroImage !== 'boolean') {
173
+ throw new Error(`generate_outro_image must be a boolean for ${context}`);
174
+ }
175
+ if (rawAddOutroAnimation !== undefined && typeof rawAddOutroAnimation !== 'boolean') {
176
+ throw new Error(`add_outro_animation must be a boolean for ${context}`);
177
+ }
178
+ if (rawAddOutroFocusArea !== undefined && typeof rawAddOutroFocusArea !== 'boolean') {
179
+ throw new Error(`add_outro_focus_area must be a boolean for ${context}`);
180
+ }
181
+ if (generateOutroImage && outroImageUrl) {
182
+ throw new Error(`Use either generate_outro_image with cta_url or outro_image_url for ${context}`);
183
+ }
184
+ if (!generateOutroImage && !outroImageUrl) {
185
+ throw new Error(`outro_image_url is required for ${context} unless generate_outro_image is true`);
186
+ }
187
+ if (generateOutroImage) {
188
+ if (!ctaUrl || !String(ctaUrl).trim()) {
189
+ throw new Error(`cta_url is required when generate_outro_image is true for ${context}`);
190
+ }
191
+ }
192
+ else if (rawAddOutroFocusArea === true && rawAddOutroAnimation !== true) {
193
+ throw new Error(`add_outro_focus_area requires add_outro_animation to be true for ${context}`);
194
+ }
195
+ if (!generateOutroImage && rawAddOutroFocusArea === true) {
196
+ if (!rawOutroFocusArea || typeof rawOutroFocusArea !== 'object' || Array.isArray(rawOutroFocusArea)) {
197
+ throw new Error(`outro_focust_area must be an object with x, y, width, height for ${context}`);
198
+ }
199
+ const { x, y, width, height } = rawOutroFocusArea;
200
+ const isInvalid = [x, y, width, height].some((value) => typeof value !== 'number' || !Number.isFinite(value));
201
+ if (isInvalid) {
202
+ throw new Error(`outro_focust_area x, y, width, height must be valid numbers for ${context}`);
203
+ }
204
+ }
205
+ return {
206
+ ...input,
207
+ videoSessionId: String(videoSessionId),
208
+ ...(outroImageUrl ? { outro_image_url: String(outroImageUrl) } : {}),
209
+ generate_outro_image: generateOutroImage,
210
+ ...(generateOutroImage ? { cta_url: String(ctaUrl).trim() } : {}),
211
+ ...(ctaTextTop ? { cta_text_top: String(ctaTextTop) } : {}),
212
+ ...(ctaTextBottom ? { cta_text_bottom: String(ctaTextBottom) } : {}),
213
+ ...(ctaLogo ? { cta_logo: String(ctaLogo) } : {}),
214
+ ...(rawAddOutroAnimation !== undefined ? { add_outro_animation: rawAddOutroAnimation === true } : {}),
215
+ ...(rawAddOutroFocusArea !== undefined ? { add_outro_focus_area: rawAddOutroFocusArea === true } : {}),
216
+ ...(rawOutroFocusArea !== undefined ? { outro_focust_area: rawOutroFocusArea } : {}),
217
+ };
218
+ }
129
219
  function normalizeSessionPublicationInput(input, context) {
130
220
  if (typeof input !== 'string' && (!input || typeof input !== 'object' || Array.isArray(input))) {
131
221
  throw new Error(`input must be a session id or object for ${context}`);
@@ -180,10 +270,7 @@ function normalizeSessionPublicationInput(input, context) {
180
270
  }
181
271
  export class SamsarClient {
182
272
  constructor(options) {
183
- if (!options?.apiKey) {
184
- throw new Error('apiKey is required to initialize SamsarClient');
185
- }
186
- this.apiKey = options.apiKey;
273
+ this.apiKey = options?.apiKey?.trim() || undefined;
187
274
  this.baseUrl = trimTrailingSlash(options.baseUrl ?? DEFAULT_BASE_URL);
188
275
  this.timeoutMs = options.timeoutMs ?? 30000;
189
276
  this.fetchFn = options.fetch ?? globalThis.fetch;
@@ -193,6 +280,155 @@ export class SamsarClient {
193
280
  throw new Error('A fetch implementation is required. Provide one via the "fetch" option when running outside environments with global fetch.');
194
281
  }
195
282
  }
283
+ /**
284
+ * Low-level POST adapter for the upcoming /v2 omni routes.
285
+ * Pass options.externalUser to include an external_user payload; omit it for internal-account calls
286
+ * or when authenticating directly with an external-user auth token/API key.
287
+ */
288
+ async postV2(path, payload = {}, options) {
289
+ return this.post(this.buildV2Url(path), this.withV2ExternalUser(payload, options), options);
290
+ }
291
+ /**
292
+ * Low-level GET adapter for the upcoming /v2 omni routes.
293
+ */
294
+ async getV2(path, options) {
295
+ const query = {
296
+ ...(options?.externalUser ? buildExternalUserQuery(options.externalUser) : {}),
297
+ ...(options?.query ?? {}),
298
+ };
299
+ return this.get(this.buildV2Url(path), { ...(options ?? {}), query });
300
+ }
301
+ async createV2Session(options) {
302
+ return this.postV2('session', {}, options);
303
+ }
304
+ async getV2Credits(options) {
305
+ return this.getV2('credits', options);
306
+ }
307
+ async getV2UserCredits(options) {
308
+ return this.getV2('user/credits', options);
309
+ }
310
+ async getV2UserUsageLogs(options) {
311
+ const query = {
312
+ ...(options?.query ?? {}),
313
+ };
314
+ if (options?.page !== undefined) {
315
+ query.page = options.page;
316
+ }
317
+ if (options?.pageSize !== undefined) {
318
+ query.pageSize = options.pageSize;
319
+ }
320
+ if (options?.limit !== undefined) {
321
+ query.limit = options.limit;
322
+ }
323
+ return this.getV2('user/usage/logs', {
324
+ ...(options ?? {}),
325
+ query,
326
+ });
327
+ }
328
+ async listV2Requests(options) {
329
+ return this.getV2('requests', options);
330
+ }
331
+ async createV2UserRechargeCredits(payload, options) {
332
+ const amount = Number(payload?.amount);
333
+ if (!Number.isFinite(amount) || amount <= 0) {
334
+ throw new Error('amount must be a positive dollar amount');
335
+ }
336
+ if (!payload?.email || typeof payload.email !== 'string') {
337
+ throw new Error('email is required');
338
+ }
339
+ const redirectUrl = payload.redirect_url ?? payload.redirectUrl;
340
+ if (!redirectUrl || typeof redirectUrl !== 'string') {
341
+ throw new Error('redirect_url is required');
342
+ }
343
+ return this.postV2('user/recharge_credits', {
344
+ ...payload,
345
+ amount,
346
+ email: payload.email.trim(),
347
+ redirect_url: redirectUrl.trim(),
348
+ }, options);
349
+ }
350
+ async refreshV2UserToken(payload, options) {
351
+ const refreshToken = typeof payload === 'string'
352
+ ? payload
353
+ : (payload?.refreshToken ?? payload?.refresh_token);
354
+ if (!refreshToken || typeof refreshToken !== 'string') {
355
+ throw new Error('refreshToken is required');
356
+ }
357
+ return this.postV2('user/refresh_token', { refreshToken: refreshToken.trim() }, options);
358
+ }
359
+ async refreshV2UserAuthToken(payload, options) {
360
+ return this.refreshV2UserToken(payload, options);
361
+ }
362
+ async getV2UserPaymentStatus(payload, options) {
363
+ const query = {
364
+ ...(options?.query ?? {}),
365
+ };
366
+ if (payload?.checkoutSessionId) {
367
+ query.checkoutSessionId = payload.checkoutSessionId;
368
+ }
369
+ if (payload?.paymentIntentId) {
370
+ query.paymentIntentId = payload.paymentIntentId;
371
+ }
372
+ if (payload?.setupIntentId) {
373
+ query.setupIntentId = payload.setupIntentId;
374
+ }
375
+ return this.getV2('user/payment_status', {
376
+ ...(options ?? {}),
377
+ query,
378
+ });
379
+ }
380
+ async createV2LoginToken(options) {
381
+ const body = options?.redirect ? { redirect: options.redirect } : {};
382
+ return this.postV2('create_login_token', body, options);
383
+ }
384
+ async createV2VideoFromText(input, options) {
385
+ const normalizedInput = normalizeCreateVideoFromTextInput(input);
386
+ return this.postV2('text_to_video', {
387
+ input: normalizedInput,
388
+ webhookUrl: options?.webhookUrl,
389
+ }, options);
390
+ }
391
+ async createV2VideoFromImageList(input, options) {
392
+ const normalizedInput = normalizeCreateVideoFromImageListInput(input);
393
+ return this.postV2('image_list_to_video', {
394
+ input: normalizedInput,
395
+ webhookUrl: options?.webhookUrl,
396
+ }, options);
397
+ }
398
+ async uploadV2ImageData(imageData, options) {
399
+ if (!Array.isArray(imageData) || imageData.length === 0) {
400
+ throw new Error('imageData must be a non-empty array of data URLs');
401
+ }
402
+ return this.postV2('upload_image_data', {
403
+ input: {
404
+ image_data: imageData,
405
+ },
406
+ }, options);
407
+ }
408
+ async updateV2VideoOutroImage(input, options) {
409
+ const normalizedInput = normalizeUpdateVideoOutroImageInput(input, 'updateV2VideoOutroImage');
410
+ return this.postV2('update_outro_image', {
411
+ input: normalizedInput,
412
+ webhookUrl: options?.webhookUrl,
413
+ }, options);
414
+ }
415
+ async addV2VideoOutroImage(input, options) {
416
+ return this.postV2('add_outro_image', {
417
+ input,
418
+ webhookUrl: options?.webhookUrl,
419
+ }, options);
420
+ }
421
+ async getV2Status(requestId, options) {
422
+ const queryParams = {
423
+ ...(options?.externalUser ? buildExternalUserQuery(options.externalUser) : {}),
424
+ ...(options?.queryParams ?? {}),
425
+ };
426
+ return this.getStatus(requestId, {
427
+ ...options,
428
+ path: this.buildV2Url('status'),
429
+ queryParams,
430
+ });
431
+ }
196
432
  /**
197
433
  * Create a new video generation job from text.
198
434
  */
@@ -563,6 +799,50 @@ export class SamsarClient {
563
799
  * only the final video render step.
564
800
  */
565
801
  async updateVideoOutroImage(input, options) {
802
+ const normalizedInput = normalizeUpdateVideoOutroImageInput(input, 'updateVideoOutroImage');
803
+ const body = {
804
+ input: normalizedInput,
805
+ webhookUrl: options?.webhookUrl,
806
+ };
807
+ const response = await this.post('video/update_outro_image', body, options);
808
+ const data = response.data;
809
+ if (data && typeof data === 'object') {
810
+ const sessionId = typeof data.sessionID === 'string'
811
+ ? data.sessionID
812
+ : typeof data.session_id === 'string'
813
+ ? data.session_id
814
+ : typeof data.request_id === 'string'
815
+ ? data.request_id
816
+ : undefined;
817
+ const normalizedSessionId = sessionId ? String(sessionId) : undefined;
818
+ const normalizedData = {
819
+ ...data,
820
+ sessionID: data.sessionID ?? normalizedSessionId ?? '',
821
+ session_id: data.session_id ?? normalizedSessionId,
822
+ request_id: data.request_id ?? normalizedSessionId,
823
+ };
824
+ return { ...response, data: normalizedData };
825
+ }
826
+ return response;
827
+ }
828
+ /**
829
+ * Update the outro image for an external-user video request by resolving the external request id
830
+ * through /external_users/update_outro_image.
831
+ */
832
+ async updateExternalVideoOutroImage(externalUser, input, options) {
833
+ const normalizedInput = normalizeUpdateVideoOutroImageInput(input, 'updateExternalVideoOutroImage');
834
+ const body = {
835
+ external_user: normalizeExternalUserIdentity(externalUser),
836
+ input: normalizedInput,
837
+ webhookUrl: options?.webhookUrl,
838
+ };
839
+ return this.post('external_users/update_outro_image', body, options);
840
+ }
841
+ /**
842
+ * Add or replace the outro image for an existing video session by cloning it into a new session and
843
+ * re-running frame/video generation.
844
+ */
845
+ async addVideoOutroImage(input, options) {
566
846
  const raw = input;
567
847
  const videoSessionId = raw.videoSessionId ??
568
848
  raw.video_session_id ??
@@ -576,8 +856,6 @@ export class SamsarClient {
576
856
  raw.outroImageUrl ??
577
857
  raw.new_outro_image_url ??
578
858
  raw.newOutroImageUrl;
579
- const rawGenerateOutroImage = raw.generate_outro_image ??
580
- raw.generateOutroImage;
581
859
  const rawAddOutroAnimation = raw.add_outro_animation ??
582
860
  raw.addOutroAnimation;
583
861
  const rawAddOutroFocusArea = raw.add_outro_focus_area ??
@@ -586,69 +864,43 @@ export class SamsarClient {
586
864
  raw.outro_focus_area ??
587
865
  raw.outroFocustArea ??
588
866
  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));
599
867
  if (!videoSessionId) {
600
- throw new Error('videoSessionId is required for updateVideoOutroImage');
868
+ throw new Error('videoSessionId is required for addVideoOutroImage');
601
869
  }
602
- if (rawGenerateOutroImage !== undefined && typeof rawGenerateOutroImage !== 'boolean') {
603
- throw new Error('generate_outro_image must be a boolean for updateVideoOutroImage');
870
+ if (!outroImageUrl) {
871
+ throw new Error('outro_image_url is required for addVideoOutroImage');
604
872
  }
605
873
  if (rawAddOutroAnimation !== undefined && typeof rawAddOutroAnimation !== 'boolean') {
606
- throw new Error('add_outro_animation must be a boolean for updateVideoOutroImage');
874
+ throw new Error('add_outro_animation must be a boolean for addVideoOutroImage');
607
875
  }
608
876
  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
- }
877
+ throw new Error('add_outro_focus_area must be a boolean for addVideoOutroImage');
621
878
  }
622
- else if (rawAddOutroFocusArea === true && rawAddOutroAnimation !== true) {
623
- throw new Error('add_outro_focus_area requires add_outro_animation to be true for updateVideoOutroImage');
879
+ if (rawAddOutroFocusArea === true && rawAddOutroAnimation !== true) {
880
+ throw new Error('add_outro_focus_area requires add_outro_animation to be true for addVideoOutroImage');
624
881
  }
625
- if (!generateOutroImage && rawAddOutroFocusArea === true) {
882
+ if (rawAddOutroFocusArea === true) {
626
883
  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');
884
+ throw new Error('outro_focust_area must be an object with x, y, width, height for addVideoOutroImage');
628
885
  }
629
886
  const { x, y, width, height } = rawOutroFocusArea;
630
887
  const isInvalid = [x, y, width, height].some((value) => typeof value !== 'number' || !Number.isFinite(value));
631
888
  if (isInvalid) {
632
- throw new Error('outro_focust_area x, y, width, height must be valid numbers for updateVideoOutroImage');
889
+ throw new Error('outro_focust_area x, y, width, height must be valid numbers for addVideoOutroImage');
633
890
  }
634
891
  }
635
892
  const body = {
636
893
  input: {
637
894
  ...input,
638
895
  videoSessionId: String(videoSessionId),
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) } : {}),
896
+ outro_image_url: String(outroImageUrl),
645
897
  ...(rawAddOutroAnimation !== undefined ? { add_outro_animation: rawAddOutroAnimation === true } : {}),
646
898
  ...(rawAddOutroFocusArea !== undefined ? { add_outro_focus_area: rawAddOutroFocusArea === true } : {}),
647
899
  ...(rawOutroFocusArea !== undefined ? { outro_focust_area: rawOutroFocusArea } : {}),
648
900
  },
649
901
  webhookUrl: options?.webhookUrl,
650
902
  };
651
- const response = await this.post('video/update_outro_image', body, options);
903
+ const response = await this.post('video/add_outro_image', body, options);
652
904
  const data = response.data;
653
905
  if (data && typeof data === 'object') {
654
906
  const sessionId = typeof data.sessionID === 'string'
@@ -670,10 +922,9 @@ export class SamsarClient {
670
922
  return response;
671
923
  }
672
924
  /**
673
- * Add or replace the outro image for an existing video session by cloning it into a new session and
674
- * re-running frame/video generation.
925
+ * Add or replace an outro image for an external-user video request by resolving the external request id.
675
926
  */
676
- async addVideoOutroImage(input, options) {
927
+ async addExternalVideoOutroImage(externalUser, input, options) {
677
928
  const raw = input;
678
929
  const videoSessionId = raw.videoSessionId ??
679
930
  raw.video_session_id ??
@@ -682,7 +933,13 @@ export class SamsarClient {
682
933
  raw.sessionId ??
683
934
  raw.sessionID ??
684
935
  raw.request_id ??
685
- raw.requestId;
936
+ raw.requestId ??
937
+ raw.source_request_id ??
938
+ raw.sourceRequestId ??
939
+ raw.external_request_id ??
940
+ raw.externalRequestId ??
941
+ raw.external_session_id ??
942
+ raw.externalSessionId;
686
943
  const outroImageUrl = raw.outro_image_url ??
687
944
  raw.outroImageUrl ??
688
945
  raw.new_outro_image_url ??
@@ -696,31 +953,32 @@ export class SamsarClient {
696
953
  raw.outroFocustArea ??
697
954
  raw.outroFocusArea;
698
955
  if (!videoSessionId) {
699
- throw new Error('videoSessionId is required for addVideoOutroImage');
956
+ throw new Error('videoSessionId is required for addExternalVideoOutroImage');
700
957
  }
701
958
  if (!outroImageUrl) {
702
- throw new Error('outro_image_url is required for addVideoOutroImage');
959
+ throw new Error('outro_image_url is required for addExternalVideoOutroImage');
703
960
  }
704
961
  if (rawAddOutroAnimation !== undefined && typeof rawAddOutroAnimation !== 'boolean') {
705
- throw new Error('add_outro_animation must be a boolean for addVideoOutroImage');
962
+ throw new Error('add_outro_animation must be a boolean for addExternalVideoOutroImage');
706
963
  }
707
964
  if (rawAddOutroFocusArea !== undefined && typeof rawAddOutroFocusArea !== 'boolean') {
708
- throw new Error('add_outro_focus_area must be a boolean for addVideoOutroImage');
965
+ throw new Error('add_outro_focus_area must be a boolean for addExternalVideoOutroImage');
709
966
  }
710
967
  if (rawAddOutroFocusArea === true && rawAddOutroAnimation !== true) {
711
- throw new Error('add_outro_focus_area requires add_outro_animation to be true for addVideoOutroImage');
968
+ throw new Error('add_outro_focus_area requires add_outro_animation to be true for addExternalVideoOutroImage');
712
969
  }
713
970
  if (rawAddOutroFocusArea === true) {
714
971
  if (!rawOutroFocusArea || typeof rawOutroFocusArea !== 'object' || Array.isArray(rawOutroFocusArea)) {
715
- throw new Error('outro_focust_area must be an object with x, y, width, height for addVideoOutroImage');
972
+ throw new Error('outro_focust_area must be an object with x, y, width, height for addExternalVideoOutroImage');
716
973
  }
717
974
  const { x, y, width, height } = rawOutroFocusArea;
718
975
  const isInvalid = [x, y, width, height].some((value) => typeof value !== 'number' || !Number.isFinite(value));
719
976
  if (isInvalid) {
720
- throw new Error('outro_focust_area x, y, width, height must be valid numbers for addVideoOutroImage');
977
+ throw new Error('outro_focust_area x, y, width, height must be valid numbers for addExternalVideoOutroImage');
721
978
  }
722
979
  }
723
980
  const body = {
981
+ external_user: normalizeExternalUserIdentity(externalUser),
724
982
  input: {
725
983
  ...input,
726
984
  videoSessionId: String(videoSessionId),
@@ -731,26 +989,7 @@ export class SamsarClient {
731
989
  },
732
990
  webhookUrl: options?.webhookUrl,
733
991
  };
734
- const response = await this.post('video/add_outro_image', body, options);
735
- const data = response.data;
736
- if (data && typeof data === 'object') {
737
- const sessionId = typeof data.sessionID === 'string'
738
- ? data.sessionID
739
- : typeof data.session_id === 'string'
740
- ? data.session_id
741
- : typeof data.request_id === 'string'
742
- ? data.request_id
743
- : undefined;
744
- const normalizedSessionId = sessionId ? String(sessionId) : undefined;
745
- const normalizedData = {
746
- ...data,
747
- sessionID: data.sessionID ?? normalizedSessionId ?? '',
748
- session_id: data.session_id ?? normalizedSessionId,
749
- request_id: data.request_id ?? normalizedSessionId,
750
- };
751
- return { ...response, data: normalizedData };
752
- }
753
- return response;
992
+ return this.post('external_users/add_outro_image', body, options);
754
993
  }
755
994
  /**
756
995
  * Fetch the latest available render URL for a given video session id.
@@ -1448,7 +1687,7 @@ export class SamsarClient {
1448
1687
  }
1449
1688
  buildHeaders(options) {
1450
1689
  const headers = {
1451
- Authorization: `Bearer ${this.apiKey}`,
1690
+ Authorization: this.apiKey ? `Bearer ${this.apiKey}` : undefined,
1452
1691
  'Content-Type': options.body ? 'application/json' : undefined,
1453
1692
  'x-external-user-api-key': options.externalUserApiKey ?? this.externalUserApiKey,
1454
1693
  ...this.defaultHeaders,
@@ -1478,6 +1717,19 @@ export class SamsarClient {
1478
1717
  const rootUrl = baseUrl.endsWith('/v1') ? baseUrl.slice(0, -3) : baseUrl;
1479
1718
  return `${rootUrl}/${cleanedPath}`;
1480
1719
  }
1720
+ withV2ExternalUser(body, options) {
1721
+ if (!options?.externalUser) {
1722
+ return body;
1723
+ }
1724
+ return {
1725
+ ...body,
1726
+ external_user: normalizeExternalUserIdentity(options.externalUser),
1727
+ };
1728
+ }
1729
+ buildV2Url(path) {
1730
+ const cleanedPath = path.replace(/^\/+/, '');
1731
+ return this.buildRootUrl(`v2/${cleanedPath}`);
1732
+ }
1481
1733
  }
1482
1734
  function trimTrailingSlash(url) {
1483
1735
  return url.replace(/\/+$/, '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "samsar-js",
3
- "version": "0.48.18",
3
+ "version": "0.48.20",
4
4
  "description": "TypeScript client for the Samsar Processor API routes.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",