diff --git a/README.md b/README.md
index 85f5b47..a8f6dc1 100644
--- a/README.md
+++ b/README.md
@@ -23,14 +23,14 @@ For Spring Boot applications, use the starter dependency for zero-configuration
ai.gradientlabs
gradient-labs-spring-boot-starter
- 1.0.0
+ 1.1.0
```
### Gradle
```gradle
-implementation 'ai.gradientlabs:gradient-labs-spring-boot-starter:1.0.0'
+implementation 'ai.gradientlabs:gradient-labs-spring-boot-starter:1.1.0'
```
### Configuration
@@ -95,14 +95,14 @@ For non-Spring applications, use the core client library:
ai.gradientlabs
gradient-labs-client
- 1.0.0
+ 1.1.0
```
### Gradle
```gradle
-implementation 'ai.gradientlabs:gradient-labs-client:1.0.0'
+implementation 'ai.gradientlabs:gradient-labs-client:1.1.0'
```
## Error Handling
diff --git a/gradient-labs-client/examples/spring-boot-example/pom.xml b/gradient-labs-client/examples/spring-boot-example/pom.xml
index 1e2888d..c03d4fe 100644
--- a/gradient-labs-client/examples/spring-boot-example/pom.xml
+++ b/gradient-labs-client/examples/spring-boot-example/pom.xml
@@ -28,7 +28,7 @@
ai.gradientlabs
gradient-labs-spring-boot-starter
- 1.0.0-SNAPSHOT
+ 1.1.0
diff --git a/gradient-labs-client/pom.xml b/gradient-labs-client/pom.xml
index f325c56..1d6f90a 100644
--- a/gradient-labs-client/pom.xml
+++ b/gradient-labs-client/pom.xml
@@ -7,7 +7,7 @@
ai.gradientlabs
gradient-labs-parent
- 1.0.1-SNAPSHOT
+ 1.1.0
gradient-labs-client
@@ -72,6 +72,20 @@
org.apache.maven.plugins
maven-source-plugin
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.4.2
+
+
+
+ true
+
+
+
+
diff --git a/gradient-labs-client/src/main/java/ai/gradientlabs/client/GradientLabsClient.java b/gradient-labs-client/src/main/java/ai/gradientlabs/client/GradientLabsClient.java
index e22ce87..ecd4134 100644
--- a/gradient-labs-client/src/main/java/ai/gradientlabs/client/GradientLabsClient.java
+++ b/gradient-labs-client/src/main/java/ai/gradientlabs/client/GradientLabsClient.java
@@ -4,6 +4,7 @@
import ai.gradientlabs.client.internal.HttpClientWrapper;
import ai.gradientlabs.client.model.*;
import ai.gradientlabs.client.request.*;
+import ai.gradientlabs.client.response.StartOutboundConversationResponse;
import ai.gradientlabs.client.webhook.Webhook;
import ai.gradientlabs.client.webhook.WebhookVerifier;
@@ -80,24 +81,68 @@ public Conversation startConversation(StartConversationRequest request) {
}
/**
- * Starts an outbound conversation.
+ * Starts an outbound live chat conversation.
*
- * Creates and starts a new outbound conversation where the AI agent proactively
- * initiates contact with a customer. The conversation follows the instructions
- * defined in the specified outbound procedure.
+ * Creates and starts a new outbound live chat conversation in which the AI agent
+ * proactively initiates contact with a customer, following the instructions defined in
+ * the specified outbound procedure.
*
- * If support_platform is not provided, the system will automatically select the
- * highest priority platform that has integration settings configured for your company.
+ * If a body is provided, that message will be sent as the opening message. Otherwise,
+ * the AI agent will generate one based on the procedure.
*
- * If body and subject are provided, that message will be sent as the initial message.
- * Otherwise, the AI agent will generate an appropriate initial message based on the procedure.
+ * The customer is created, or matched to an existing record, from the customer ID and
+ * any customer support platform identifiers you supply. The platform the chat is
+ * delivered on needs an identifier for that customer.
*
- * @param request the outbound conversation parameters
+ * @param request the outbound chat conversation parameters
* @return the response containing the conversation ID
* @throws GradientLabsException if the request fails
*/
- public ai.gradientlabs.client.response.StartOutboundConversationResponse startOutboundConversation(StartOutboundConversationRequest request) {
- return httpClient.post("/outbound/conversations", request, ai.gradientlabs.client.response.StartOutboundConversationResponse.class);
+ public StartOutboundConversationResponse startOutboundChatConversation(StartOutboundChatConversationRequest request) {
+ return httpClient.post("/outbound/conversations/chat", request, StartOutboundConversationResponse.class);
+ }
+
+ /**
+ * Starts an outbound email conversation.
+ *
+ * Creates and starts a new outbound email conversation in which the AI agent
+ * proactively initiates contact with a customer, following the instructions defined in
+ * the specified outbound procedure.
+ *
+ * If a subject and body are provided, that email will be sent as the opening message.
+ * Otherwise, the AI agent will write one based on the procedure.
+ *
+ * The customer is created, or matched to an existing record, from the customer ID and
+ * any customer support platform identifiers you supply. The platform the email is sent
+ * from needs an identifier for that customer, so sending from Zendesk needs a Zendesk
+ * identifier, and so on.
+ *
+ * @param request the outbound email conversation parameters
+ * @return the response containing the conversation ID
+ * @throws GradientLabsException if the request fails
+ */
+ public StartOutboundConversationResponse startOutboundEmailConversation(StartOutboundEmailConversationRequest request) {
+ return httpClient.post("/outbound/conversations/email", request, StartOutboundConversationResponse.class);
+ }
+
+ /**
+ * Places an outbound phone call.
+ *
+ * Places an outbound phone call in which the AI agent proactively contacts a customer,
+ * following the instructions defined in the specified outbound procedure.
+ *
+ * The number the call is placed from must already be provisioned for your company.
+ *
+ * The customer is created, or matched to an existing record, from the customer ID and
+ * any customer support platform identifiers you supply. The dialled number is recorded
+ * against that same customer.
+ *
+ * @param request the outbound phone conversation parameters
+ * @return the response containing the conversation ID
+ * @throws GradientLabsException if the request fails
+ */
+ public StartOutboundConversationResponse startOutboundPhoneConversation(StartOutboundPhoneConversationRequest request) {
+ return httpClient.post("/outbound/conversations/phone", request, StartOutboundConversationResponse.class);
}
/**
diff --git a/gradient-labs-client/src/main/java/ai/gradientlabs/client/request/StartOutboundChatConversationRequest.java b/gradient-labs-client/src/main/java/ai/gradientlabs/client/request/StartOutboundChatConversationRequest.java
new file mode 100644
index 0000000..54ca783
--- /dev/null
+++ b/gradient-labs-client/src/main/java/ai/gradientlabs/client/request/StartOutboundChatConversationRequest.java
@@ -0,0 +1,219 @@
+package ai.gradientlabs.client.request;
+
+import ai.gradientlabs.client.model.CustomerSupportPlatformIdentifier;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Parameters for starting an outbound live chat conversation.
+ *
+ * This kicks off a proactive conversation where your AI agent initiates contact with a
+ * customer over chat.
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class StartOutboundChatConversationRequest {
+
+ @JsonProperty("customer_id")
+ private final String customerId;
+
+ @JsonProperty("customer_support_platform_identifiers")
+ private final List customerSupportPlatformIdentifiers;
+
+ @JsonProperty("procedure_id")
+ private final String procedureId;
+
+ @JsonProperty("support_platform")
+ private final String supportPlatform;
+
+ @JsonProperty("body")
+ private final String body;
+
+ @JsonProperty("resources")
+ private final Map resources;
+
+ private StartOutboundChatConversationRequest(Builder builder) {
+ this.customerId = builder.customerId;
+ this.customerSupportPlatformIdentifiers = builder.customerSupportPlatformIdentifiers;
+ this.procedureId = builder.procedureId;
+ this.supportPlatform = builder.supportPlatform;
+ this.body = builder.body;
+ this.resources = builder.resources;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public String getCustomerId() {
+ return customerId;
+ }
+
+ public List getCustomerSupportPlatformIdentifiers() {
+ return customerSupportPlatformIdentifiers;
+ }
+
+ public String getProcedureId() {
+ return procedureId;
+ }
+
+ public String getSupportPlatform() {
+ return supportPlatform;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ public Map getResources() {
+ return resources;
+ }
+
+ /**
+ * The support platforms an outbound chat can be delivered on.
+ */
+ public static class SupportPlatform {
+ public static final String INTERCOM = "intercom";
+
+ /**
+ * Delivers the conversation to your own webhook endpoint.
+ */
+ public static final String PUBLIC_API = "public-api";
+ }
+
+ public static class Builder {
+ private String customerId;
+ private List customerSupportPlatformIdentifiers;
+ private String procedureId;
+ private String supportPlatform;
+ private String body;
+ private Map resources;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets your own identifier for the customer, as used in your systems (required).
+ *
+ * It is stored as the customer's company customer ID, and is the identifier echoed
+ * back to you in tool and webhook payloads.
+ *
+ * @param customerId the customer ID
+ * @return this builder
+ */
+ public Builder customerId(String customerId) {
+ this.customerId = customerId;
+ return this;
+ }
+
+ /**
+ * Sets the customer's identifiers in third-party customer support platforms (optional).
+ *
+ * These are added to the customer alongside {@code customerId}, and can be used to
+ * match against customers created via those platforms' native integrations. The
+ * platform named in {@code supportPlatform} needs an identifier here, unless the
+ * customer already carries one from an earlier conversation.
+ *
+ * @param customerSupportPlatformIdentifiers the customer support platform identifiers
+ * @return this builder
+ */
+ public Builder customerSupportPlatformIdentifiers(List customerSupportPlatformIdentifiers) {
+ this.customerSupportPlatformIdentifiers = customerSupportPlatformIdentifiers;
+ return this;
+ }
+
+ /**
+ * Adds a single customer support platform identifier.
+ *
+ * @param customerSupportPlatformIdentifier the identifier to add
+ * @return this builder
+ */
+ public Builder addCustomerSupportPlatformIdentifier(CustomerSupportPlatformIdentifier customerSupportPlatformIdentifier) {
+ if (this.customerSupportPlatformIdentifiers == null) {
+ this.customerSupportPlatformIdentifiers = new ArrayList<>();
+ }
+ this.customerSupportPlatformIdentifiers.add(customerSupportPlatformIdentifier);
+ return this;
+ }
+
+ /**
+ * Sets the procedure ID (required).
+ *
+ * The ID of the outbound procedure that defines what the AI agent should accomplish
+ * in this conversation. The procedure must be of type "outbound", must be live
+ * (deployed), and must be enabled for the chat channel.
+ *
+ * @param procedureId the procedure ID
+ * @return this builder
+ */
+ public Builder procedureId(String procedureId) {
+ this.procedureId = procedureId;
+ return this;
+ }
+
+ /**
+ * Sets the support platform the chat is delivered on (required).
+ *
+ * Use constants from {@link SupportPlatform}.
+ *
+ * @param supportPlatform the support platform
+ * @return this builder
+ */
+ public Builder supportPlatform(String supportPlatform) {
+ this.supportPlatform = supportPlatform;
+ return this;
+ }
+
+ /**
+ * Sets the content of the initial message to send to the customer (optional).
+ *
+ * If omitted, the AI agent will generate an appropriate opening message based on the
+ * procedure.
+ *
+ * @param body the body
+ * @return this builder
+ */
+ public Builder body(String body) {
+ this.body = body;
+ return this;
+ }
+
+ /**
+ * Sets resources (optional).
+ *
+ * A JSON object containing structured data that the AI agent can use during the
+ * conversation. This should be organized as a map where keys are resource type names
+ * and values are the corresponding data.
+ * Example: {"customer_profile": {"tier": "premium", "lifetime_value": 5000}}
+ *
+ * @param resources the resources
+ * @return this builder
+ */
+ public Builder resources(Map resources) {
+ this.resources = resources;
+ return this;
+ }
+
+ /**
+ * Builds the request.
+ *
+ * @return a new request instance
+ * @throws IllegalArgumentException if required fields are missing
+ */
+ public StartOutboundChatConversationRequest build() {
+ if (customerId == null || customerId.isBlank()) {
+ throw new IllegalArgumentException("customerId is required");
+ }
+ if (procedureId == null || procedureId.isBlank()) {
+ throw new IllegalArgumentException("procedureId is required");
+ }
+ if (supportPlatform == null || supportPlatform.isBlank()) {
+ throw new IllegalArgumentException("supportPlatform is required");
+ }
+ return new StartOutboundChatConversationRequest(this);
+ }
+ }
+}
diff --git a/gradient-labs-client/src/main/java/ai/gradientlabs/client/request/StartOutboundConversationRequest.java b/gradient-labs-client/src/main/java/ai/gradientlabs/client/request/StartOutboundEmailConversationRequest.java
similarity index 50%
rename from gradient-labs-client/src/main/java/ai/gradientlabs/client/request/StartOutboundConversationRequest.java
rename to gradient-labs-client/src/main/java/ai/gradientlabs/client/request/StartOutboundEmailConversationRequest.java
index 4f9aea0..e64d509 100644
--- a/gradient-labs-client/src/main/java/ai/gradientlabs/client/request/StartOutboundConversationRequest.java
+++ b/gradient-labs-client/src/main/java/ai/gradientlabs/client/request/StartOutboundEmailConversationRequest.java
@@ -1,24 +1,26 @@
package ai.gradientlabs.client.request;
+import ai.gradientlabs.client.model.CustomerSupportPlatformIdentifier;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
/**
- * Parameters for starting an outbound conversation.
+ * Parameters for starting an outbound email conversation.
*
- * This kicks off a proactive conversation where your AI agent initiates
- * contact with a customer.
+ * This kicks off a proactive conversation where your AI agent emails a customer.
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
-public class StartOutboundConversationRequest {
+public class StartOutboundEmailConversationRequest {
@JsonProperty("customer_id")
private final String customerId;
- @JsonProperty("customer_source")
- private final String customerSource;
+ @JsonProperty("customer_support_platform_identifiers")
+ private final List customerSupportPlatformIdentifiers;
@JsonProperty("procedure_id")
private final String procedureId;
@@ -26,9 +28,6 @@ public class StartOutboundConversationRequest {
@JsonProperty("support_platform")
private final String supportPlatform;
- @JsonProperty("channel")
- private final String channel;
-
@JsonProperty("subject")
private final String subject;
@@ -38,12 +37,11 @@ public class StartOutboundConversationRequest {
@JsonProperty("resources")
private final Map resources;
- private StartOutboundConversationRequest(Builder builder) {
+ private StartOutboundEmailConversationRequest(Builder builder) {
this.customerId = builder.customerId;
- this.customerSource = builder.customerSource;
+ this.customerSupportPlatformIdentifiers = builder.customerSupportPlatformIdentifiers;
this.procedureId = builder.procedureId;
this.supportPlatform = builder.supportPlatform;
- this.channel = builder.channel;
this.subject = builder.subject;
this.body = builder.body;
this.resources = builder.resources;
@@ -57,8 +55,8 @@ public String getCustomerId() {
return customerId;
}
- public String getCustomerSource() {
- return customerSource;
+ public List getCustomerSupportPlatformIdentifiers() {
+ return customerSupportPlatformIdentifiers;
}
public String getProcedureId() {
@@ -69,10 +67,6 @@ public String getSupportPlatform() {
return supportPlatform;
}
- public String getChannel() {
- return channel;
- }
-
public String getSubject() {
return subject;
}
@@ -86,46 +80,24 @@ public Map getResources() {
}
/**
- * Customer source constants.
+ * The support platforms an outbound email can be sent from.
*/
- public static class CustomerSource {
+ public static class SupportPlatform {
public static final String INTERCOM = "intercom";
- public static final String FRESHCHAT = "freshchat";
- public static final String FRESHDESK = "freshdesk";
- public static final String PUBLIC_API = "public-api";
- public static final String SALESFORCE = "salesforce";
public static final String ZENDESK = "zendesk";
- public static final String VOICE = "livekit";
- public static final String VOICE_TWILIO = "twilio";
- public static final String VOICE_TALKDESK = "talkdesk";
- public static final String VOICE_INTERCOM = "intercom-voice";
- public static final String WEB_APP = "web-app";
- public static final String FILE = "file";
- }
+ public static final String SALESFORCE = "salesforce";
- /**
- * Support platform constants.
- */
- public static class SupportPlatform {
- public static final String FRESHCHAT = "freshchat";
- public static final String FRESHDESK = "freshdesk";
- public static final String INTERCOM = "intercom";
+ /**
+ * Delivers the conversation to your own webhook endpoint.
+ */
public static final String PUBLIC_API = "public-api";
- public static final String SALESFORCE = "salesforce";
- public static final String ZENDESK = "zendesk";
- public static final String VOICE = "livekit";
- public static final String VOICE_TWILIO = "twilio";
- public static final String VOICE_TALKDESK = "talkdesk";
- public static final String VOICE_INTERCOM = "intercom-voice";
- public static final String WEB_APP = "web-app";
}
public static class Builder {
private String customerId;
- private String customerSource;
+ private List customerSupportPlatformIdentifiers;
private String procedureId;
private String supportPlatform;
- private String channel;
private String subject;
private String body;
private Map resources;
@@ -134,11 +106,10 @@ private Builder() {
}
/**
- * Sets the customer ID (required).
+ * Sets your own identifier for the customer, as used in your systems (required).
*
- * The external identifier for the customer in your support platform.
- * For Intercom, this is the external ID you've defined for the user (e.g., "user-123456").
- * For other platforms, this is the customer identifier used by that platform.
+ * It is stored as the customer's company customer ID, and is the identifier echoed
+ * back to you in tool and webhook payloads.
*
* @param customerId the customer ID
* @return this builder
@@ -149,17 +120,33 @@ public Builder customerId(String customerId) {
}
/**
- * Sets the customer source (required).
+ * Sets the customer's identifiers in third-party customer support platforms (optional).
*
- * The source of the customer data. For example, a customer ID and phone number
- * might be from Intercom, but the outbound conversation is initiated via Twilio.
- * Use constants from {@link CustomerSource}.
+ * These are added to the customer alongside {@code customerId}, and can be used to
+ * match against customers created via those platforms' native integrations. The
+ * platform named in {@code supportPlatform} needs an identifier here, unless the
+ * customer already carries one from an earlier conversation. Zendesk requires type
+ * {@code zendesk_support_user}; Salesforce requires type {@code salesforce_contact_id}.
*
- * @param customerSource the customer source
+ * @param customerSupportPlatformIdentifiers the customer support platform identifiers
* @return this builder
*/
- public Builder customerSource(String customerSource) {
- this.customerSource = customerSource;
+ public Builder customerSupportPlatformIdentifiers(List customerSupportPlatformIdentifiers) {
+ this.customerSupportPlatformIdentifiers = customerSupportPlatformIdentifiers;
+ return this;
+ }
+
+ /**
+ * Adds a single customer support platform identifier.
+ *
+ * @param customerSupportPlatformIdentifier the identifier to add
+ * @return this builder
+ */
+ public Builder addCustomerSupportPlatformIdentifier(CustomerSupportPlatformIdentifier customerSupportPlatformIdentifier) {
+ if (this.customerSupportPlatformIdentifiers == null) {
+ this.customerSupportPlatformIdentifiers = new ArrayList<>();
+ }
+ this.customerSupportPlatformIdentifiers.add(customerSupportPlatformIdentifier);
return this;
}
@@ -167,7 +154,8 @@ public Builder customerSource(String customerSource) {
* Sets the procedure ID (required).
*
* The ID of the outbound procedure that defines what the AI agent should accomplish
- * in this conversation. The procedure must be of type "outbound" and must be live (deployed).
+ * in this conversation. The procedure must be of type "outbound", must be live
+ * (deployed), and must be enabled for the email channel.
*
* @param procedureId the procedure ID
* @return this builder
@@ -178,12 +166,8 @@ public Builder procedureId(String procedureId) {
}
/**
- * Sets the support platform (optional).
+ * Sets the support platform the email is sent from (required).
*
- * The support platform where the conversation should be created.
- * Valid values include "intercom", "zendesk", "freshdesk", "freshchat".
- * If not provided, the system will automatically select the first connected platform
- * in priority order: intercom, zendesk, freshchat, freshdesk, public-api.
* Use constants from {@link SupportPlatform}.
*
* @param supportPlatform the support platform
@@ -195,26 +179,10 @@ public Builder supportPlatform(String supportPlatform) {
}
/**
- * Sets the channel (optional).
+ * Sets the subject line for the initial email (optional).
*
- * Specifies the communication channel for this conversation.
- * If not provided, defaults to "email".
- * Valid values: "email", "web", "sms", "voice", etc.
- *
- * @param channel the channel
- * @return this builder
- */
- public Builder channel(String channel) {
- this.channel = channel;
- return this;
- }
-
- /**
- * Sets the subject (optional).
- *
- * The subject line for the initial message (primarily used for email channels).
- * Only used if body is also provided. If both subject and body are omitted,
- * the AI agent will generate the initial message.
+ * Required if a body is provided, and forbidden otherwise. If both are omitted, the
+ * AI agent will write the opening email.
*
* @param subject the subject
* @return this builder
@@ -225,11 +193,10 @@ public Builder subject(String subject) {
}
/**
- * Sets the body (optional).
+ * Sets the content of the initial email to send to the customer (optional).
*
- * The content of the initial message to send to the customer.
- * If provided, this message will be sent instead of having the AI agent generate one.
- * If omitted, the AI agent will generate an appropriate initial message based on the procedure.
+ * Required if a subject is provided, and forbidden otherwise. If both are omitted,
+ * the AI agent will write the opening email.
*
* @param body the body
* @return this builder
@@ -242,11 +209,10 @@ public Builder body(String body) {
/**
* Sets resources (optional).
*
- * A JSON object containing structured data that the AI agent can use during the conversation.
- * This should be organized as a map where keys are resource type names and values are the
- * corresponding data.
+ * A JSON object containing structured data that the AI agent can use during the
+ * conversation. This should be organized as a map where keys are resource type names
+ * and values are the corresponding data.
* Example: {"customer_profile": {"tier": "premium", "lifetime_value": 5000}}
- * The data will be made available to the AI agent for context during conversation processing.
*
* @param resources the resources
* @return this builder
@@ -260,19 +226,25 @@ public Builder resources(Map resources) {
* Builds the request.
*
* @return a new request instance
- * @throws IllegalArgumentException if required fields are missing
+ * @throws IllegalArgumentException if required fields are missing, or if only one of
+ * subject and body is set
*/
- public StartOutboundConversationRequest build() {
+ public StartOutboundEmailConversationRequest build() {
if (customerId == null || customerId.isBlank()) {
throw new IllegalArgumentException("customerId is required");
}
- if (customerSource == null || customerSource.isBlank()) {
- throw new IllegalArgumentException("customerSource is required");
- }
if (procedureId == null || procedureId.isBlank()) {
throw new IllegalArgumentException("procedureId is required");
}
- return new StartOutboundConversationRequest(this);
+ if (supportPlatform == null || supportPlatform.isBlank()) {
+ throw new IllegalArgumentException("supportPlatform is required");
+ }
+ boolean hasSubject = subject != null && !subject.isBlank();
+ boolean hasBody = body != null && !body.isBlank();
+ if (hasSubject != hasBody) {
+ throw new IllegalArgumentException("subject and body must be set together, or both omitted");
+ }
+ return new StartOutboundEmailConversationRequest(this);
}
}
}
diff --git a/gradient-labs-client/src/main/java/ai/gradientlabs/client/request/StartOutboundPhoneConversationRequest.java b/gradient-labs-client/src/main/java/ai/gradientlabs/client/request/StartOutboundPhoneConversationRequest.java
new file mode 100644
index 0000000..230ec39
--- /dev/null
+++ b/gradient-labs-client/src/main/java/ai/gradientlabs/client/request/StartOutboundPhoneConversationRequest.java
@@ -0,0 +1,208 @@
+package ai.gradientlabs.client.request;
+
+import ai.gradientlabs.client.model.CustomerSupportPlatformIdentifier;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Parameters for placing an outbound phone call.
+ *
+ * This kicks off a proactive conversation where your AI agent calls a customer and works
+ * through an outbound procedure.
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class StartOutboundPhoneConversationRequest {
+
+ @JsonProperty("customer_id")
+ private final String customerId;
+
+ @JsonProperty("customer_support_platform_identifiers")
+ private final List customerSupportPlatformIdentifiers;
+
+ @JsonProperty("procedure_id")
+ private final String procedureId;
+
+ @JsonProperty("to_phone_number")
+ private final String toPhoneNumber;
+
+ @JsonProperty("from_phone_number")
+ private final String fromPhoneNumber;
+
+ @JsonProperty("resources")
+ private final Map resources;
+
+ private StartOutboundPhoneConversationRequest(Builder builder) {
+ this.customerId = builder.customerId;
+ this.customerSupportPlatformIdentifiers = builder.customerSupportPlatformIdentifiers;
+ this.procedureId = builder.procedureId;
+ this.toPhoneNumber = builder.toPhoneNumber;
+ this.fromPhoneNumber = builder.fromPhoneNumber;
+ this.resources = builder.resources;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public String getCustomerId() {
+ return customerId;
+ }
+
+ public List getCustomerSupportPlatformIdentifiers() {
+ return customerSupportPlatformIdentifiers;
+ }
+
+ public String getProcedureId() {
+ return procedureId;
+ }
+
+ public String getToPhoneNumber() {
+ return toPhoneNumber;
+ }
+
+ public String getFromPhoneNumber() {
+ return fromPhoneNumber;
+ }
+
+ public Map getResources() {
+ return resources;
+ }
+
+ public static class Builder {
+ private String customerId;
+ private List customerSupportPlatformIdentifiers;
+ private String procedureId;
+ private String toPhoneNumber;
+ private String fromPhoneNumber;
+ private Map resources;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets your own identifier for the customer, as used in your systems (required).
+ *
+ * It is stored as the customer's company customer ID, and is the identifier echoed
+ * back to you in tool and webhook payloads.
+ *
+ * @param customerId the customer ID
+ * @return this builder
+ */
+ public Builder customerId(String customerId) {
+ this.customerId = customerId;
+ return this;
+ }
+
+ /**
+ * Sets the customer's identifiers in third-party customer support platforms (optional).
+ *
+ * These are added to the customer alongside {@code customerId}, and can be used to
+ * match against customers created via those platforms' native integrations, and to
+ * pull that platform's customer data into the call as context.
+ *
+ * @param customerSupportPlatformIdentifiers the customer support platform identifiers
+ * @return this builder
+ */
+ public Builder customerSupportPlatformIdentifiers(List customerSupportPlatformIdentifiers) {
+ this.customerSupportPlatformIdentifiers = customerSupportPlatformIdentifiers;
+ return this;
+ }
+
+ /**
+ * Adds a single customer support platform identifier.
+ *
+ * @param customerSupportPlatformIdentifier the identifier to add
+ * @return this builder
+ */
+ public Builder addCustomerSupportPlatformIdentifier(CustomerSupportPlatformIdentifier customerSupportPlatformIdentifier) {
+ if (this.customerSupportPlatformIdentifiers == null) {
+ this.customerSupportPlatformIdentifiers = new ArrayList<>();
+ }
+ this.customerSupportPlatformIdentifiers.add(customerSupportPlatformIdentifier);
+ return this;
+ }
+
+ /**
+ * Sets the procedure ID (required).
+ *
+ * The ID of the outbound procedure that defines what the AI agent should accomplish
+ * on the call. The procedure must be of type "outbound", must be live (deployed), and
+ * must be enabled for the "voice" channel.
+ *
+ * @param procedureId the procedure ID
+ * @return this builder
+ */
+ public Builder procedureId(String procedureId) {
+ this.procedureId = procedureId;
+ return this;
+ }
+
+ /**
+ * Sets the customer's phone number to dial (required).
+ *
+ * E.164 format, e.g. "+14155551234".
+ *
+ * @param toPhoneNumber the number to dial
+ * @return this builder
+ */
+ public Builder toPhoneNumber(String toPhoneNumber) {
+ this.toPhoneNumber = toPhoneNumber;
+ return this;
+ }
+
+ /**
+ * Sets the caller ID to place the call from (required).
+ *
+ * E.164 format. It must be a phone number already provisioned for your company.
+ *
+ * @param fromPhoneNumber the caller ID
+ * @return this builder
+ */
+ public Builder fromPhoneNumber(String fromPhoneNumber) {
+ this.fromPhoneNumber = fromPhoneNumber;
+ return this;
+ }
+
+ /**
+ * Sets resources (optional).
+ *
+ * A JSON object containing structured data that the AI agent can use during the
+ * conversation. This should be organized as a map where keys are resource type names
+ * and values are the corresponding data.
+ * Example: {"customer_profile": {"tier": "premium", "lifetime_value": 5000}}
+ *
+ * @param resources the resources
+ * @return this builder
+ */
+ public Builder resources(Map resources) {
+ this.resources = resources;
+ return this;
+ }
+
+ /**
+ * Builds the request.
+ *
+ * @return a new request instance
+ * @throws IllegalArgumentException if required fields are missing
+ */
+ public StartOutboundPhoneConversationRequest build() {
+ if (customerId == null || customerId.isBlank()) {
+ throw new IllegalArgumentException("customerId is required");
+ }
+ if (procedureId == null || procedureId.isBlank()) {
+ throw new IllegalArgumentException("procedureId is required");
+ }
+ if (toPhoneNumber == null || toPhoneNumber.isBlank()) {
+ throw new IllegalArgumentException("toPhoneNumber is required");
+ }
+ if (fromPhoneNumber == null || fromPhoneNumber.isBlank()) {
+ throw new IllegalArgumentException("fromPhoneNumber is required");
+ }
+ return new StartOutboundPhoneConversationRequest(this);
+ }
+ }
+}
diff --git a/gradient-labs-client/src/test/java/ai/gradientlabs/client/request/StartOutboundConversationRequestTest.java b/gradient-labs-client/src/test/java/ai/gradientlabs/client/request/StartOutboundConversationRequestTest.java
new file mode 100644
index 0000000..734b694
--- /dev/null
+++ b/gradient-labs-client/src/test/java/ai/gradientlabs/client/request/StartOutboundConversationRequestTest.java
@@ -0,0 +1,150 @@
+package ai.gradientlabs.client.request;
+
+import ai.gradientlabs.client.model.CustomerSupportPlatformIdentifier;
+import ai.gradientlabs.client.model.CustomerSupportPlatformIdentifierType;
+import ai.gradientlabs.client.model.SupportPlatform;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class StartOutboundConversationRequestTest {
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Test
+ void serializesChatRequest() {
+ StartOutboundChatConversationRequest request = StartOutboundChatConversationRequest.builder()
+ .customerId("user-1")
+ .procedureId("procedure-1")
+ .supportPlatform(StartOutboundChatConversationRequest.SupportPlatform.INTERCOM)
+ .body("Hi there")
+ .addCustomerSupportPlatformIdentifier(new CustomerSupportPlatformIdentifier(
+ SupportPlatform.INTERCOM,
+ CustomerSupportPlatformIdentifierType.INTERCOM_USER,
+ "6953e162a988d9ef0f73ef9b"))
+ .resources(Map.of("customer_profile", Map.of("tier", "premium")))
+ .build();
+
+ JsonNode json = objectMapper.valueToTree(request);
+
+ assertEquals("user-1", json.get("customer_id").asText());
+ assertEquals("procedure-1", json.get("procedure_id").asText());
+ assertEquals("intercom", json.get("support_platform").asText());
+ assertEquals("Hi there", json.get("body").asText());
+ assertEquals("premium", json.get("resources").get("customer_profile").get("tier").asText());
+
+ JsonNode identifier = json.get("customer_support_platform_identifiers").get(0);
+ assertEquals("intercom", identifier.get("support_platform").asText());
+ assertEquals("intercom_user", identifier.get("type").asText());
+ assertEquals("6953e162a988d9ef0f73ef9b", identifier.get("value").asText());
+ }
+
+ @Test
+ void omitsUnsetOptionalChatFields() {
+ StartOutboundChatConversationRequest request = StartOutboundChatConversationRequest.builder()
+ .customerId("user-1")
+ .procedureId("procedure-1")
+ .supportPlatform(StartOutboundChatConversationRequest.SupportPlatform.PUBLIC_API)
+ .build();
+
+ JsonNode json = objectMapper.valueToTree(request);
+
+ assertFalse(json.has("body"));
+ assertFalse(json.has("resources"));
+ assertFalse(json.has("customer_support_platform_identifiers"));
+ }
+
+ @Test
+ void chatRequiresSupportPlatform() {
+ StartOutboundChatConversationRequest.Builder builder = StartOutboundChatConversationRequest.builder()
+ .customerId("user-1")
+ .procedureId("procedure-1");
+
+ assertThrows(IllegalArgumentException.class, builder::build);
+ }
+
+ @Test
+ void serializesEmailRequest() {
+ StartOutboundEmailConversationRequest request = StartOutboundEmailConversationRequest.builder()
+ .customerId("user-1")
+ .procedureId("procedure-1")
+ .supportPlatform(StartOutboundEmailConversationRequest.SupportPlatform.ZENDESK)
+ .subject("Your order")
+ .body("It has shipped.")
+ .addCustomerSupportPlatformIdentifier(new CustomerSupportPlatformIdentifier(
+ SupportPlatform.ZENDESK,
+ CustomerSupportPlatformIdentifierType.ZENDESK_SUPPORT_USER,
+ "42"))
+ .build();
+
+ JsonNode json = objectMapper.valueToTree(request);
+
+ assertEquals("zendesk", json.get("support_platform").asText());
+ assertEquals("Your order", json.get("subject").asText());
+ assertEquals("It has shipped.", json.get("body").asText());
+ assertEquals("zendesk_support_user",
+ json.get("customer_support_platform_identifiers").get(0).get("type").asText());
+ }
+
+ @Test
+ void emailRejectsSubjectWithoutBody() {
+ StartOutboundEmailConversationRequest.Builder builder = StartOutboundEmailConversationRequest.builder()
+ .customerId("user-1")
+ .procedureId("procedure-1")
+ .supportPlatform(StartOutboundEmailConversationRequest.SupportPlatform.INTERCOM)
+ .subject("Your order");
+
+ assertThrows(IllegalArgumentException.class, builder::build);
+ }
+
+ @Test
+ void emailRejectsBodyWithoutSubject() {
+ StartOutboundEmailConversationRequest.Builder builder = StartOutboundEmailConversationRequest.builder()
+ .customerId("user-1")
+ .procedureId("procedure-1")
+ .supportPlatform(StartOutboundEmailConversationRequest.SupportPlatform.INTERCOM)
+ .body("It has shipped.");
+
+ assertThrows(IllegalArgumentException.class, builder::build);
+ }
+
+ @Test
+ void serializesPhoneRequest() {
+ StartOutboundPhoneConversationRequest request = StartOutboundPhoneConversationRequest.builder()
+ .customerId("user-1")
+ .procedureId("procedure-1")
+ .toPhoneNumber("+14155551234")
+ .fromPhoneNumber("+14155555678")
+ .build();
+
+ JsonNode json = objectMapper.valueToTree(request);
+
+ assertEquals("+14155551234", json.get("to_phone_number").asText());
+ assertEquals("+14155555678", json.get("from_phone_number").asText());
+ assertFalse(json.has("support_platform"));
+ assertFalse(json.has("channel"));
+ }
+
+ @Test
+ void phoneRequiresBothPhoneNumbers() {
+ StartOutboundPhoneConversationRequest.Builder missingFrom = StartOutboundPhoneConversationRequest.builder()
+ .customerId("user-1")
+ .procedureId("procedure-1")
+ .toPhoneNumber("+14155551234");
+
+ assertThrows(IllegalArgumentException.class, missingFrom::build);
+
+ StartOutboundPhoneConversationRequest.Builder missingTo = StartOutboundPhoneConversationRequest.builder()
+ .customerId("user-1")
+ .procedureId("procedure-1")
+ .fromPhoneNumber("+14155555678");
+
+ assertThrows(IllegalArgumentException.class, missingTo::build);
+ }
+}
diff --git a/gradient-labs-spring-boot-starter/README.md b/gradient-labs-spring-boot-starter/README.md
index 65ae322..c118c41 100644
--- a/gradient-labs-spring-boot-starter/README.md
+++ b/gradient-labs-spring-boot-starter/README.md
@@ -20,14 +20,14 @@ This starter provides zero-configuration setup for Spring Boot applications usin
ai.gradientlabs
gradient-labs-spring-boot-starter
- 1.0.0
+ 1.1.0
```
### Gradle
```gradle
-implementation 'ai.gradientlabs:gradient-labs-spring-boot-starter:1.0.0'
+implementation 'ai.gradientlabs:gradient-labs-spring-boot-starter:1.1.0'
```
## Configuration
diff --git a/gradient-labs-spring-boot-starter/pom.xml b/gradient-labs-spring-boot-starter/pom.xml
index 834b0c2..39a7118 100644
--- a/gradient-labs-spring-boot-starter/pom.xml
+++ b/gradient-labs-spring-boot-starter/pom.xml
@@ -7,7 +7,7 @@
ai.gradientlabs
gradient-labs-parent
- 1.0.1-SNAPSHOT
+ 1.1.0
gradient-labs-spring-boot-starter
diff --git a/pom.xml b/pom.xml
index 8873162..f802f46 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
ai.gradientlabs
gradient-labs-parent
- 1.0.1-SNAPSHOT
+ 1.1.0
pom
Gradient Labs Java SDK