@vuevox/sdk 0.7.0 → 0.8.0

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
@@ -31,6 +31,7 @@ hello:read
31
31
  spaces:read
32
32
  agents:read
33
33
  calls:read
34
+ calls:write
34
35
  leads:read
35
36
  leads:write
36
37
  lead_custom_fields:manage
@@ -104,6 +105,9 @@ vuevox.agents.list();
104
105
  vuevox.agents.paginate();
105
106
  vuevox.calls.list();
106
107
  vuevox.calls.get("call-id");
108
+ vuevox.calls.upload({ idempotencyKey: "crm-call-123", spaceId: "space-id", agent: { externalId: "agent-1", name: "Morgan" }, lead: { externalId: "lead-1", firstName: "Ada", lastName: "Lovelace" }, audio: { file: audioBlob, filename: "call.mp3" } });
109
+ vuevox.calls.queueAnalysis("call-id");
110
+ vuevox.calls.waitForAnalysis("call-id");
107
111
  vuevox.calls.paginate();
108
112
  vuevox.leads.list();
109
113
  vuevox.leads.get("lead-id");
@@ -312,6 +316,106 @@ console.log(response.data.data.transcript);
312
316
 
313
317
  Returns: `Promise<VueVoxApiResponse<CallDetailResponse>>`.
314
318
 
319
+ ### `vuevox.calls.upload(input)`
320
+
321
+ Uploads an audio recording, upserts the agent and lead by `externalId`, creates the call in an existing platform-managed space, and optionally queues analysis.
322
+
323
+ Required scope: `calls:write`.
324
+
325
+ The API requires an idempotency key for uploads. Reusing the same `idempotencyKey` with the same request returns the original response; reusing it with different metadata or audio returns an `idempotency_key_conflict` error.
326
+
327
+ Spaces are managed in VueVox. Use `vuevox.spaces.list()` to obtain the `spaceId`.
328
+
329
+ The maximum audio upload size is configured by a VueVox superadmin and defaults to 20 MB. Your server/proxy upload limits must also allow that size.
330
+
331
+ Options: `UploadCallInput`
332
+
333
+ | Option | Type | Description |
334
+ | --- | --- | --- |
335
+ | `idempotencyKey` | `string` | Required unique key for safe retries. |
336
+ | `spaceId` | `string` | Required existing VueVox space ID. |
337
+ | `externalId` | `string \| null` | Optional call ID from your system. |
338
+ | `queueAnalysis` | `boolean` | Optional. Defaults to `true`; set `false` to upload now and queue later. |
339
+ | `agent.externalId` | `string` | Required agent ID from your system. |
340
+ | `agent.name` | `string` | Required when creating a new agent. |
341
+ | `agent.email` | `string \| null` | Optional agent email. |
342
+ | `agent.phone` | `string \| null` | Optional agent phone. |
343
+ | `lead.externalId` | `string` | Required lead/prospect ID from your system. |
344
+ | `lead.firstName` | `string` | Required when creating a new lead. |
345
+ | `lead.lastName` | `string` | Required when creating a new lead. |
346
+ | `lead.email` | `string \| null` | Optional lead email. |
347
+ | `lead.phone` | `string \| null` | Optional lead phone. |
348
+ | `lead.customFields` | `Record<string, unknown>` | Optional organization-defined lead custom fields. |
349
+ | `audio.file` | `Blob` | Required audio file blob. In Node, use `new Blob([buffer], { type: "audio/mpeg" })`. |
350
+ | `audio.filename` | `string` | Optional filename sent in multipart upload. |
351
+
352
+ ```ts
353
+ import { readFile } from "node:fs/promises";
354
+
355
+ const buffer = await readFile("./call.mp3");
356
+
357
+ const response = await vuevox.calls.upload({
358
+ idempotencyKey: "crm-call-789",
359
+ externalId: "crm-call-789",
360
+ spaceId: "space-id",
361
+ queueAnalysis: true,
362
+ agent: {
363
+ externalId: "agent-123",
364
+ name: "Morgan Agent",
365
+ },
366
+ lead: {
367
+ externalId: "prospect-456",
368
+ firstName: "Ada",
369
+ lastName: "Lovelace",
370
+ email: "ada@example.com",
371
+ customFields: {
372
+ crm_stage: "qualified",
373
+ },
374
+ },
375
+ audio: {
376
+ file: new Blob([buffer], { type: "audio/mpeg" }),
377
+ filename: "call.mp3",
378
+ },
379
+ });
380
+
381
+ console.log(response.data.data.id, response.data.data.analysisStatus, response.requestId);
382
+ ```
383
+
384
+ Returns: `Promise<VueVoxApiResponse<CallResponse>>`.
385
+
386
+ ### `vuevox.calls.queueAnalysis(callId)`
387
+
388
+ Queues analysis for a call that was uploaded with `queueAnalysis: false`.
389
+
390
+ Required scope: `calls:write`.
391
+
392
+ ```ts
393
+ const response = await vuevox.calls.queueAnalysis("call-id");
394
+ console.log(response.data.data.analysisStatus, response.data.data.queueStatus);
395
+ ```
396
+
397
+ Returns: `Promise<VueVoxApiResponse<CallResponse>>`.
398
+
399
+ ### `vuevox.calls.waitForAnalysis(callId, options?)`
400
+
401
+ Polls `vuevox.calls.get(callId)` until the call analysis status is `completed` or `failed`.
402
+
403
+ Required scope: `calls:read`.
404
+
405
+ Options: `WaitForAnalysisOptions`
406
+
407
+ | Option | Type | Description |
408
+ | --- | --- | --- |
409
+ | `intervalMs` | `number` | Poll interval. Defaults to `2000`. |
410
+ | `timeoutMs` | `number` | Timeout before throwing `analysis_timeout`. Defaults to `120000`. |
411
+
412
+ ```ts
413
+ const response = await vuevox.calls.waitForAnalysis("call-id", { timeoutMs: 180000 });
414
+ console.log(response.data.data.analysis);
415
+ ```
416
+
417
+ Returns: `Promise<VueVoxApiResponse<CallDetailResponse>>`.
418
+
315
419
  ### `vuevox.calls.paginate(options?)`
316
420
 
317
421
  Iterates organization calls across all pages.
@@ -551,6 +655,14 @@ insufficient_scope
551
655
  rate_limited
552
656
  invalid_request
553
657
  call_not_found
658
+ call_external_id_conflict
659
+ analysis_already_queued
660
+ analysis_timeout
661
+ ai_config_missing
662
+ evaluation_grid_missing
663
+ audio_not_found
664
+ idempotency_key_conflict
665
+ idempotency_key_in_progress
554
666
  lead_not_found
555
667
  custom_field_not_found
556
668
  custom_field_conflict
@@ -608,8 +720,10 @@ import type {
608
720
  Agent,
609
721
  AgentsListResponse,
610
722
  CallDetailResponse,
723
+ CallResponse,
611
724
  CallsListResponse,
612
725
  CallSummary,
726
+ CallUploadMetadata,
613
727
  CustomFieldFilters,
614
728
  CustomFieldFilterValue,
615
729
  HelloResponse,
@@ -630,11 +744,13 @@ import type {
630
744
  ListSpacesOptions,
631
745
  Space,
632
746
  SpacesListResponse,
747
+ UploadCallInput,
633
748
  VueVoxApiResponse,
634
749
  VueVoxClientOptions,
635
750
  VueVoxErrorResponse,
636
751
  VueVoxResponseEvent,
637
752
  VueVoxResponseMetadata,
753
+ WaitForAnalysisOptions,
638
754
  } from "@vuevox/sdk";
639
755
  ```
640
756
 
package/dist/client.d.ts CHANGED
@@ -4,6 +4,8 @@ export type SpacesListResponse = components["schemas"]["SpacesListResponse"];
4
4
  export type AgentsListResponse = components["schemas"]["AgentsListResponse"];
5
5
  export type CallsListResponse = components["schemas"]["CallsListResponse"];
6
6
  export type CallDetailResponse = components["schemas"]["CallDetailResponse"];
7
+ export type CallResponse = components["schemas"]["CallResponse"];
8
+ export type CallUploadMetadata = components["schemas"]["CallUploadMetadata"];
7
9
  export type LeadsListResponse = components["schemas"]["LeadsListResponse"];
8
10
  export type LeadDetailResponse = components["schemas"]["LeadDetailResponse"];
9
11
  export type LeadCustomFieldsListResponse = components["schemas"]["LeadCustomFieldsListResponse"];
@@ -40,6 +42,17 @@ export interface ListCallsOptions extends ListSpacesOptions {
40
42
  createdBefore?: string;
41
43
  leadCustomFields?: CustomFieldFilters;
42
44
  }
45
+ export interface UploadCallInput extends CallUploadMetadata {
46
+ idempotencyKey: string;
47
+ audio: {
48
+ file: Blob;
49
+ filename?: string;
50
+ };
51
+ }
52
+ export interface WaitForAnalysisOptions {
53
+ intervalMs?: number;
54
+ timeoutMs?: number;
55
+ }
43
56
  export interface ListAgentsOptions extends ListSpacesOptions {
44
57
  spaceId?: string;
45
58
  }
@@ -104,8 +117,12 @@ export declare function createVueVoxClient(options: VueVoxClientOptions): {
104
117
  calls: {
105
118
  list: (listOptions?: ListCallsOptions) => Promise<VueVoxApiResponse<CallsListResponse>>;
106
119
  get: (callId: string) => Promise<VueVoxApiResponse<CallDetailResponse>>;
120
+ upload: (input: UploadCallInput) => Promise<VueVoxApiResponse<CallResponse>>;
121
+ queueAnalysis: (callId: string) => Promise<VueVoxApiResponse<CallResponse>>;
122
+ waitForAnalysis: (callId: string, waitOptions?: WaitForAnalysisOptions) => Promise<VueVoxApiResponse<CallDetailResponse>>;
107
123
  paginate: (listOptions?: ListCallsOptions) => AsyncGenerator<{
108
124
  id: string;
125
+ externalId: string | null;
109
126
  space: components["schemas"]["ResourceSummary"];
110
127
  lead: components["schemas"]["LeadSummary"];
111
128
  agent: components["schemas"]["AgentSummary"];
package/dist/client.js CHANGED
@@ -46,6 +46,34 @@ export function createVueVoxClient(options) {
46
46
  async function getCall(callId) {
47
47
  return apiGet(`/v1/calls/${encodeURIComponent(callId)}`);
48
48
  }
49
+ async function uploadCall(input) {
50
+ const { audio, idempotencyKey, ...metadata } = input;
51
+ const formData = new FormData();
52
+ formData.set("metadata", JSON.stringify(metadata));
53
+ formData.set("audioFile", audio.file, audio.filename ?? "call-audio");
54
+ return apiMultipart("POST", "/v1/calls", formData, {
55
+ "Idempotency-Key": idempotencyKey,
56
+ });
57
+ }
58
+ async function queueCallAnalysis(callId) {
59
+ return apiJson("POST", `/v1/calls/${encodeURIComponent(callId)}/analysis-jobs`, {});
60
+ }
61
+ async function waitForCallAnalysis(callId, waitOptions = {}) {
62
+ const intervalMs = waitOptions.intervalMs ?? 2_000;
63
+ const timeoutMs = waitOptions.timeoutMs ?? 120_000;
64
+ const startedAt = Date.now();
65
+ for (;;) {
66
+ const response = await getCall(callId);
67
+ const status = response.data.data.analysisStatus;
68
+ if (status === "completed" || status === "failed") {
69
+ return response;
70
+ }
71
+ if (Date.now() - startedAt >= timeoutMs) {
72
+ throw new VueVoxApiError(408, "analysis_timeout", "Timed out waiting for call analysis.", undefined, response.requestId);
73
+ }
74
+ await sleep(intervalMs);
75
+ }
76
+ }
49
77
  async function listLeads(listOptions = {}) {
50
78
  return apiGet("/v1/leads", listOptions);
51
79
  }
@@ -90,6 +118,18 @@ export function createVueVoxClient(options) {
90
118
  });
91
119
  return withMetadata(result.data, result.response, result.requestId);
92
120
  }
121
+ async function apiMultipart(method, path, body, headers) {
122
+ const accessToken = await getAccessToken();
123
+ const result = await requestJson(method, path, {
124
+ method,
125
+ headers: {
126
+ Authorization: `Bearer ${accessToken}`,
127
+ ...headers,
128
+ },
129
+ body,
130
+ });
131
+ return withMetadata(result.data, result.response, result.requestId);
132
+ }
93
133
  async function requestJson(method, path, init) {
94
134
  for (let attempt = 0;; attempt++) {
95
135
  const response = await fetchFn(buildUrl(baseUrl, path, init.query), init);
@@ -133,6 +173,9 @@ export function createVueVoxClient(options) {
133
173
  calls: {
134
174
  list: listCalls,
135
175
  get: getCall,
176
+ upload: uploadCall,
177
+ queueAnalysis: queueCallAnalysis,
178
+ waitForAnalysis: waitForCallAnalysis,
136
179
  paginate: (listOptions = {}) => paginate(listCalls, listOptions),
137
180
  },
138
181
  leads: {
@@ -97,7 +97,11 @@ export interface paths {
97
97
  */
98
98
  get: operations["listCalls"];
99
99
  put?: never;
100
- post?: never;
100
+ /**
101
+ * Upload a call and optionally queue analysis
102
+ * @description Uploads a call recording, upserts the agent and lead by externalId, creates the call in an existing platform-managed space, and optionally queues analysis. Requires an Idempotency-Key header for safe retries. The audio size limit is configured by a VueVox superadmin and defaults to 20 MB.
103
+ */
104
+ post: operations["uploadCall"];
101
105
  delete?: never;
102
106
  options?: never;
103
107
  head?: never;
@@ -124,6 +128,26 @@ export interface paths {
124
128
  patch?: never;
125
129
  trace?: never;
126
130
  };
131
+ "/v1/calls/{callId}/analysis-jobs": {
132
+ parameters: {
133
+ query?: never;
134
+ header?: never;
135
+ path?: never;
136
+ cookie?: never;
137
+ };
138
+ get?: never;
139
+ put?: never;
140
+ /**
141
+ * Queue analysis for a call
142
+ * @description Queues analysis for a previously uploaded organization call. Use this when a call was uploaded with queueAnalysis set to false.
143
+ */
144
+ post: operations["queueCallAnalysis"];
145
+ delete?: never;
146
+ options?: never;
147
+ head?: never;
148
+ patch?: never;
149
+ trace?: never;
150
+ };
127
151
  "/v1/leads": {
128
152
  parameters: {
129
153
  query?: never;
@@ -243,7 +267,7 @@ export interface components {
243
267
  client_secret: string;
244
268
  /**
245
269
  * @description Space-separated scopes requested for the access token.
246
- * @example hello:read spaces:read agents:read calls:read leads:read leads:write lead_custom_fields:manage
270
+ * @example hello:read spaces:read agents:read calls:read calls:write leads:read leads:write lead_custom_fields:manage
247
271
  */
248
272
  scope?: string;
249
273
  };
@@ -253,7 +277,7 @@ export interface components {
253
277
  token_type: "Bearer";
254
278
  /** @example 3600 */
255
279
  expires_in: number;
256
- /** @example hello:read spaces:read agents:read calls:read leads:read leads:write lead_custom_fields:manage */
280
+ /** @example hello:read spaces:read agents:read calls:read calls:write leads:read leads:write lead_custom_fields:manage */
257
281
  scope: string;
258
282
  };
259
283
  HelloResponse: {
@@ -317,6 +341,8 @@ export interface components {
317
341
  CallSummary: {
318
342
  /** Format: uuid */
319
343
  id: string;
344
+ /** @description Optional ID from an external system. */
345
+ externalId: string | null;
320
346
  space: components["schemas"]["ResourceSummary"];
321
347
  lead: components["schemas"]["LeadSummary"];
322
348
  agent: components["schemas"]["AgentSummary"];
@@ -357,6 +383,44 @@ export interface components {
357
383
  CallDetailResponse: {
358
384
  data: components["schemas"]["CallDetail"];
359
385
  };
386
+ CallResponse: {
387
+ data: components["schemas"]["CallSummary"];
388
+ };
389
+ CallUploadMetadata: {
390
+ /** @description Optional call ID from your system. Must be unique within the organization when provided. */
391
+ externalId?: string | null;
392
+ /**
393
+ * Format: uuid
394
+ * @description Existing VueVox space ID. Spaces are managed in the VueVox platform.
395
+ */
396
+ spaceId: string;
397
+ /**
398
+ * @description When true or omitted, queues analysis immediately after upload. When false, queue later with queueCallAnalysis.
399
+ * @default true
400
+ */
401
+ queueAnalysis: boolean;
402
+ agent: components["schemas"]["CallUploadAgent"];
403
+ lead: components["schemas"]["CallUploadLead"];
404
+ };
405
+ CallUploadAgent: {
406
+ externalId: string;
407
+ /** @description Required when creating a new agent. */
408
+ name?: string;
409
+ /** Format: email */
410
+ email?: string | null;
411
+ phone?: string | null;
412
+ };
413
+ CallUploadLead: {
414
+ externalId: string;
415
+ /** @description Required when creating a new lead. */
416
+ firstName?: string;
417
+ /** @description Required when creating a new lead. */
418
+ lastName?: string;
419
+ /** Format: email */
420
+ email?: string | null;
421
+ phone?: string | null;
422
+ customFields?: components["schemas"]["CustomFields"];
423
+ };
360
424
  Lead: {
361
425
  /** Format: uuid */
362
426
  id: string;
@@ -789,6 +853,77 @@ export interface operations {
789
853
  };
790
854
  };
791
855
  };
856
+ uploadCall: {
857
+ parameters: {
858
+ query?: never;
859
+ header: {
860
+ /** @description Unique key for this upload attempt. Reusing the same key with the same request returns the original response; reusing it with a different request returns a conflict. */
861
+ "Idempotency-Key": string;
862
+ };
863
+ path?: never;
864
+ cookie?: never;
865
+ };
866
+ requestBody: {
867
+ content: {
868
+ "multipart/form-data": {
869
+ /** @description JSON string matching CallUploadMetadata. */
870
+ metadata: string;
871
+ /**
872
+ * Format: binary
873
+ * @description Audio file. Supported extensions include wav, mp3, aac, m4a, amr, mpeg, mpga, x-wav, ogg, oga, mp4, and mp4a.
874
+ */
875
+ audioFile: string;
876
+ };
877
+ };
878
+ };
879
+ responses: {
880
+ /** @description Call uploaded. Analysis is queued when metadata.queueAnalysis is true. */
881
+ 201: {
882
+ headers: {
883
+ [name: string]: unknown;
884
+ };
885
+ content: {
886
+ "application/json": components["schemas"]["CallResponse"];
887
+ };
888
+ };
889
+ /** @description Bearer token is missing, invalid, or expired. */
890
+ 401: {
891
+ headers: {
892
+ [name: string]: unknown;
893
+ };
894
+ content: {
895
+ "application/json": components["schemas"]["ErrorResponse"];
896
+ };
897
+ };
898
+ /** @description Bearer token does not include the required scope. */
899
+ 403: {
900
+ headers: {
901
+ [name: string]: unknown;
902
+ };
903
+ content: {
904
+ "application/json": components["schemas"]["ErrorResponse"];
905
+ };
906
+ };
907
+ /** @description Idempotency key conflict, external call ID conflict, or analysis already queued. */
908
+ 409: {
909
+ headers: {
910
+ [name: string]: unknown;
911
+ };
912
+ content: {
913
+ "application/json": components["schemas"]["ErrorResponse"];
914
+ };
915
+ };
916
+ /** @description Multipart upload or metadata failed validation. */
917
+ 422: {
918
+ headers: {
919
+ [name: string]: unknown;
920
+ };
921
+ content: {
922
+ "application/json": components["schemas"]["ErrorResponse"];
923
+ };
924
+ };
925
+ };
926
+ };
792
927
  getCall: {
793
928
  parameters: {
794
929
  query?: never;
@@ -852,6 +987,74 @@ export interface operations {
852
987
  };
853
988
  };
854
989
  };
990
+ queueCallAnalysis: {
991
+ parameters: {
992
+ query?: never;
993
+ header?: never;
994
+ path: {
995
+ /** @description Call ID. */
996
+ callId: string;
997
+ };
998
+ cookie?: never;
999
+ };
1000
+ requestBody?: never;
1001
+ responses: {
1002
+ /** @description Analysis queued. */
1003
+ 200: {
1004
+ headers: {
1005
+ [name: string]: unknown;
1006
+ };
1007
+ content: {
1008
+ "application/json": components["schemas"]["CallResponse"];
1009
+ };
1010
+ };
1011
+ /** @description Bearer token is missing, invalid, or expired. */
1012
+ 401: {
1013
+ headers: {
1014
+ [name: string]: unknown;
1015
+ };
1016
+ content: {
1017
+ "application/json": components["schemas"]["ErrorResponse"];
1018
+ };
1019
+ };
1020
+ /** @description Bearer token does not include the required scope. */
1021
+ 403: {
1022
+ headers: {
1023
+ [name: string]: unknown;
1024
+ };
1025
+ content: {
1026
+ "application/json": components["schemas"]["ErrorResponse"];
1027
+ };
1028
+ };
1029
+ /** @description Call was not found in the API client's organization. */
1030
+ 404: {
1031
+ headers: {
1032
+ [name: string]: unknown;
1033
+ };
1034
+ content: {
1035
+ "application/json": components["schemas"]["ErrorResponse"];
1036
+ };
1037
+ };
1038
+ /** @description Analysis is already queued or processing. */
1039
+ 409: {
1040
+ headers: {
1041
+ [name: string]: unknown;
1042
+ };
1043
+ content: {
1044
+ "application/json": components["schemas"]["ErrorResponse"];
1045
+ };
1046
+ };
1047
+ /** @description Organization AI settings, evaluation grid, or audio file is missing. */
1048
+ 422: {
1049
+ headers: {
1050
+ [name: string]: unknown;
1051
+ };
1052
+ content: {
1053
+ "application/json": components["schemas"]["ErrorResponse"];
1054
+ };
1055
+ };
1056
+ };
1057
+ };
855
1058
  listLeads: {
856
1059
  parameters: {
857
1060
  query?: {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { createVueVoxClient } from "./client.js";
2
- export type { Agent, AgentsListResponse, CallDetailResponse, CallsListResponse, CallSummary, CustomFieldFilters, CustomFieldFilterValue, HelloResponse, Lead, LeadCustomField, LeadCustomFieldCreateRequest, LeadCustomFieldResponse, LeadCustomFieldUpdateRequest, LeadCustomFieldsListResponse, LeadDetailResponse, LeadUpdateRequest, LeadUpsertRequest, LeadsListResponse, ListAgentsOptions, ListCallsOptions, ListLeadCustomFieldsOptions, ListLeadsOptions, ListSpacesOptions, Space, SpacesListResponse, VueVoxApiResponse, VueVoxClientOptions, VueVoxResponseEvent, VueVoxResponseMetadata, } from "./client.js";
2
+ export type { Agent, AgentsListResponse, CallDetailResponse, CallResponse, CallsListResponse, CallSummary, CallUploadMetadata, CustomFieldFilters, CustomFieldFilterValue, HelloResponse, Lead, LeadCustomField, LeadCustomFieldCreateRequest, LeadCustomFieldResponse, LeadCustomFieldUpdateRequest, LeadCustomFieldsListResponse, LeadDetailResponse, LeadUpdateRequest, LeadUpsertRequest, LeadsListResponse, ListAgentsOptions, ListCallsOptions, ListLeadCustomFieldsOptions, ListLeadsOptions, ListSpacesOptions, Space, SpacesListResponse, UploadCallInput, VueVoxApiResponse, VueVoxClientOptions, VueVoxResponseEvent, VueVoxResponseMetadata, WaitForAnalysisOptions, } from "./client.js";
3
3
  export { VueVoxApiError } from "./errors.js";
4
4
  export type { VueVoxErrorResponse } from "./errors.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vuevox/sdk",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "TypeScript SDK for the VueVox Developer API.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",