diff --git a/.gitignore b/.gitignore
index ef86395..0e743b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,3 +34,4 @@ dependency-reduced-pom.xml
mem*.db
README.html
+/CLAUDE.md
diff --git a/client-lib/pom.xml b/client-lib/pom.xml
index 0550326..9fdb659 100644
--- a/client-lib/pom.xml
+++ b/client-lib/pom.xml
@@ -5,7 +5,7 @@
com.signnow
api-client-lib
- 1.0.0-SNAPSHOT
+ 1.0.8
jar
@@ -94,4 +94,13 @@
+
+
+
+ github
+ Darwin
+ https://maven.pkg.github.com/SSDevelopment/DarwinOperations
+
+
+
diff --git a/client-lib/src/main/java/com/signnow/library/SNClient.java b/client-lib/src/main/java/com/signnow/library/SNClient.java
index ebbc099..5b9ad4c 100644
--- a/client-lib/src/main/java/com/signnow/library/SNClient.java
+++ b/client-lib/src/main/java/com/signnow/library/SNClient.java
@@ -5,13 +5,11 @@
import com.signnow.library.dto.User;
import com.signnow.library.exceptions.SNApiException;
import com.signnow.library.exceptions.SNException;
-import com.signnow.library.facades.DocumentGroups;
-import com.signnow.library.facades.Documents;
-import com.signnow.library.facades.ServiceProvider;
-import com.signnow.library.facades.Templates;
+import com.signnow.library.facades.*;
import com.signnow.library.services.DocumentGroupsService;
import com.signnow.library.services.DocumentsService;
import com.signnow.library.services.TemplatesService;
+import com.signnow.library.services.WebhookService;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
@@ -36,6 +34,7 @@ public class SNClient implements ServiceProvider {
private WebTarget apiWebTarget;
private Documents documentsService = new DocumentsService(this);
private Templates templatesService = new TemplatesService(this);
+ private Webhooks webhooksService = new WebhookService(this);
private DocumentGroups documentGroupsService = new DocumentGroupsService(this);
protected SNClient(WebTarget apiWebTarget, User user) {
@@ -61,6 +60,11 @@ public Templates templatesService() {
return templatesService;
}
+ @Override
+ public Webhooks webhooksService() {
+ return webhooksService;
+ }
+
@Override
public DocumentGroups documentGroupsService() {
return documentGroupsService;
diff --git a/client-lib/src/main/java/com/signnow/library/dto/Document.java b/client-lib/src/main/java/com/signnow/library/dto/Document.java
index b442028..409b987 100644
--- a/client-lib/src/main/java/com/signnow/library/dto/Document.java
+++ b/client-lib/src/main/java/com/signnow/library/dto/Document.java
@@ -2,8 +2,7 @@
import com.fasterxml.jackson.annotation.*;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Document extends GenericId {
@@ -28,6 +27,9 @@ public class Document extends GenericId {
* Free form invites info
*/
public List requests;
+ @JsonProperty("fields")
+ public List fields;
+ public List roles;
public static class SigningLinkRequest {
@JsonProperty("document_id")
@@ -127,6 +129,14 @@ public String toString() {
}
}
+ public static class PrefillTextRequest {
+ public final List fields;
+
+ public PrefillTextRequest(List fields) {
+ this.fields = fields;
+ }
+ }
+
public static class FieldsUpdateRequest {
public final List fields;
@@ -135,6 +145,19 @@ public FieldsUpdateRequest(List fields) {
}
}
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public static class FieldText {
+ @JsonProperty("field_name")
+ public String fieldName;
+ @JsonProperty("prefilled_text")
+ public String prefilledText;
+
+ public FieldText(String name, String prefill) {
+ this.fieldName = name;
+ this.prefilledText = prefill;
+ }
+ }
+
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Field {
public int x;
@@ -155,11 +178,70 @@ public String getType() {
}
}
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public static class FieldMetadata {
+ public String id;
+ public String type;
+ @JsonProperty("role_id")
+ public String roleId;
+ public String role;
+ public String originator;
+ public String fulfiller;
+ @JsonProperty("json_attributes")
+ public Attributes attributes;
+ @JsonProperty("field_request_id")
+ public String fieldRequestId;
+ @JsonProperty("element_id")
+ public String elementId;
+ @JsonProperty("field_request_canceled")
+ public boolean fieldRequestCanceled;
+ @JsonProperty("template_field_id")
+ public String templateFieldId;
+ @JsonProperty("field_id")
+ public String fieldId;
+
+ public String getType() {
+ return type.toString();
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Attributes {
+ @JsonProperty("page_number")
+ public String pageNumber;
+ public int x;
+ public int y;
+ public int width;
+ public int height;
+ public boolean required;
+ public String name;
+ public String label;
+ @JsonProperty("prefilled_text")
+ public String prefilledText;
+ }
+ }
+
@JsonIgnoreProperties(ignoreUnknown = true)
public static class FieldInvite extends GenericId {
public String status;
public String email;
public String role;
+ @JsonProperty("role_id")
+ public String roleId;
+ @JsonProperty("is_embedded")
+ public Boolean isEmbedded;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Role {
+ @JsonProperty("unique_id")
+ public String uniqueId;
+ /**
+ * SignNow returns this number as a string, same as {@link Document#pageCount}
+ */
+ @JsonProperty("signing_order")
+ public String signingOrder;
+ public String name;
}
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -175,4 +257,78 @@ public static class DocumentDownloadLink {
public String link;
}
+ /*------------------------Embedded invites------------------------*/
+ public static class EmbeddedSigningInviteRequest {
+ public final List invites = new ArrayList<>();
+
+ public EmbeddedSigningInviteRequest(EmbeddedInvite... invite) {
+ this.invites.addAll(Arrays.asList(invite));
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public static class EmbeddedInvite {
+ public String email;
+ public String role_id;
+ public String role;
+ public int order = 1;
+ @JsonProperty("auth_method")
+ public String authMethod = "none";
+ @JsonProperty("first_name")
+ public String firstName;
+ @JsonProperty("last_name")
+ public String lastName;
+ public String language;
+
+ /**
+ * @param email signer email
+ * @param role role NAME (e.g. "Signer 1"), not the role id. To create an invite
+ * by role id use {@link #forRoleId(String, String)}.
+ */
+ public EmbeddedInvite(String email, String role) {
+ this.email = email;
+ this.role = role;
+ }
+
+ /**
+ * Creates an invite addressed by role id (the {@code unique_id} of a {@link Role}).
+ *
+ * @param email signer email
+ * @param roleId role id from {@link Document#roles} ({@link Role#uniqueId})
+ */
+ public static EmbeddedInvite forRoleId(String email, String roleId) {
+ EmbeddedInvite invite = new EmbeddedInvite(email, null);
+ invite.role_id = roleId;
+ return invite;
+ }
+ }
+
+ public static class EmbeddedSigningInviteResponse {
+ public List data = new ArrayList<>();
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public static class InviteResponseData {
+ public String id;
+ public String email;
+ @JsonProperty("role_id")
+ public String roleId;
+ public int order;
+ public String status;
+ }
+
+ public static class EmbeddedInviteLinkRequest {
+ @JsonProperty("link_expiration")
+ public int linkExpiration = 45;
+ @JsonProperty("auth_method")
+ public String authMethod = "none";
+ }
+
+ public static class EmbeddedInviteLinkResponse {
+ public Map data = new HashMap<>();
+
+ public String getLink() {
+ return data.get("link");
+ }
+ }
}
diff --git a/client-lib/src/main/java/com/signnow/library/dto/DocumentGroup.java b/client-lib/src/main/java/com/signnow/library/dto/DocumentGroup.java
index 33f4af0..e7a69e7 100644
--- a/client-lib/src/main/java/com/signnow/library/dto/DocumentGroup.java
+++ b/client-lib/src/main/java/com/signnow/library/dto/DocumentGroup.java
@@ -1,11 +1,15 @@
package com.signnow.library.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@JsonIgnoreProperties(ignoreUnknown = true)
public class DocumentGroup extends GenericId {
@@ -57,4 +61,145 @@ public static class DocumentGroupsListResponce {
@JsonProperty("document_group_total_count")
public Integer totalCount;
}
+
+ /*------------------------Embedded group invites------------------------*/
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public static class EmbeddedGroupInviteRequest {
+ public final List invites;
+ /**
+ * When true the signer gets one merged signing session for the whole group
+ */
+ @JsonProperty("sign_as_merged")
+ public Boolean signAsMerged = Boolean.TRUE;
+
+ public EmbeddedGroupInviteRequest(EmbeddedGroupInviteStep... steps) {
+ this.invites = new ArrayList<>(Arrays.asList(steps));
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public static class EmbeddedGroupInviteStep {
+ public final int order;
+ public final List signers;
+
+ public EmbeddedGroupInviteStep(int order, EmbeddedGroupInviteSigner... signers) {
+ this.order = order;
+ this.signers = new ArrayList<>(Arrays.asList(signers));
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public static class EmbeddedGroupInviteSigner {
+ public String email;
+ @JsonProperty("auth_method")
+ public String authMethod = "none";
+ @JsonProperty("first_name")
+ public String firstName;
+ @JsonProperty("last_name")
+ public String lastName;
+ public String language;
+ public final List documents;
+
+ public EmbeddedGroupInviteSigner(String email, EmbeddedGroupInviteDocument... documents) {
+ this.email = email;
+ this.documents = new ArrayList<>(Arrays.asList(documents));
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public static class EmbeddedGroupInviteDocument {
+ public final String id;
+ public String action = "sign";
+ public final String role;
+
+ /**
+ * @param documentId id of a document belonging to the group
+ * @param role role NAME (e.g. "Signer 1", as listed in {@link DocumentInfo#roles}),
+ * NOT a role id — unlike the per-document embedded invite API
+ */
+ public EmbeddedGroupInviteDocument(String documentId, String role) {
+ this.id = documentId;
+ this.role = role;
+ }
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class EmbeddedGroupInviteResponse {
+ public InviteData data;
+
+ public String getId() {
+ return data != null ? data.id : null;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class InviteData {
+ public String id;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public static class EmbeddedGroupInviteLinkRequest {
+ /**
+ * Identifies the signer the link is minted for (unlike the per-document link
+ * mint, which addresses the signer in the URL)
+ */
+ public String email;
+ @JsonProperty("auth_method")
+ public String authMethod = "none";
+ @JsonProperty("link_expiration")
+ public Integer linkExpiration = 45;
+
+ public EmbeddedGroupInviteLinkRequest(String email) {
+ this.email = email;
+ }
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class EmbeddedGroupInviteLinkResponse {
+ public Map data = new HashMap<>();
+
+ public String getLink() {
+ return data.get("link");
+ }
+ }
+
+ /*------------------------Document group info v2------------------------*/
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class DocumentGroupV2Response {
+ public DocumentGroupV2Info data;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class DocumentGroupV2Info {
+ public String id;
+ public String name;
+ @JsonProperty("invite_id")
+ public String inviteId;
+ @JsonProperty("pending_step_id")
+ public String pendingStepId;
+ public String state;
+ @JsonProperty("last_invite_id")
+ public String lastInviteId;
+ public List documents = new ArrayList<>();
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class DocumentGroupV2Document {
+ public String id;
+ public List roles = new ArrayList<>();
+ @JsonProperty("document_name")
+ public String documentName;
+ @JsonProperty("field_invites")
+ public List fieldInvites = new ArrayList<>();
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class DocumentGroupV2FieldInvite {
+ public String id;
+ public String status;
+ @JsonProperty("signer_email")
+ public String signerEmail;
+ }
}
diff --git a/client-lib/src/main/java/com/signnow/library/dto/Webhook.java b/client-lib/src/main/java/com/signnow/library/dto/Webhook.java
new file mode 100644
index 0000000..a11115d
--- /dev/null
+++ b/client-lib/src/main/java/com/signnow/library/dto/Webhook.java
@@ -0,0 +1,37 @@
+package com.signnow.library.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Map;
+
+public class Webhook {
+ public static class CreateRequest {
+ @JsonProperty("event")
+ public final String event;
+ @JsonProperty("entity_id")
+ public final String entityId;
+ @JsonProperty("action")
+ public final String action = "callback";
+ @JsonProperty("attributes")
+ public final Attributes attributes;
+
+ public CreateRequest(String event, String entityId, String callback, Map headers) {
+ this.event = event;
+ this.entityId = entityId;
+ this.attributes = new Attributes(callback, headers);
+ }
+ }
+
+ static class Attributes {
+ @JsonProperty("callback")
+ public final String callback;
+
+ @JsonProperty("headers")
+ public final Map headers;
+
+ Attributes(String callback, Map headers) {
+ this.callback = callback;
+ this.headers = headers;
+ }
+ }
+}
diff --git a/client-lib/src/main/java/com/signnow/library/facades/DocumentGroups.java b/client-lib/src/main/java/com/signnow/library/facades/DocumentGroups.java
index 5a2a35d..45280fb 100644
--- a/client-lib/src/main/java/com/signnow/library/facades/DocumentGroups.java
+++ b/client-lib/src/main/java/com/signnow/library/facades/DocumentGroups.java
@@ -21,4 +21,12 @@ public interface DocumentGroups {
String createDocumentGroupInvite(String documentGroupId, GroupInvite groupInvite) throws SNException;
void resendInvites(String documentGroupId, String inviteId, String email) throws SNException;
+
+ DocumentGroup.EmbeddedGroupInviteResponse createDocumentGroupEmbeddedInvite(String documentGroupId, DocumentGroup.EmbeddedGroupInviteRequest request) throws SNException;
+
+ String getDocumentGroupEmbeddedInviteLink(String documentGroupId, String embeddedInviteId, DocumentGroup.EmbeddedGroupInviteLinkRequest request) throws SNException;
+
+ DocumentGroup.DocumentGroupV2Response getDocumentGroupV2(String documentGroupId) throws SNException;
+
+ void deleteDocumentGroupEmbeddedInvite(String documentGroupId) throws SNException;
}
diff --git a/client-lib/src/main/java/com/signnow/library/facades/Documents.java b/client-lib/src/main/java/com/signnow/library/facades/Documents.java
index a1a6539..b496375 100644
--- a/client-lib/src/main/java/com/signnow/library/facades/Documents.java
+++ b/client-lib/src/main/java/com/signnow/library/facades/Documents.java
@@ -22,11 +22,20 @@ public interface Documents {
void sendDocumentSignInvite(String documentId, Document.SigningInviteWithRolesRequest request) throws SNException;
+ Document.EmbeddedSigningInviteResponse createDocumentEmbeddedSignInvite(String documentId, Document.EmbeddedSigningInviteRequest request) throws SNException;
+
+ String getDocumentEmbeddedSignInviteLink(String documentId, String inviteId, Document.EmbeddedInviteLinkRequest request) throws SNException;
+
+ void deleteDocumentEmbeddedSignInvite(String documentId) throws SNException;
+
void updateDocumentFields(String documentId, List request) throws SNException;
+ void prefillText(String documentId, List request) throws SNException;
+
Document getDocument(String documentId) throws SNException;
void deleteDocument(String documentId) throws SNException;
String getDownloadLink(String documentId) throws SNException;
+
}
diff --git a/client-lib/src/main/java/com/signnow/library/facades/ServiceProvider.java b/client-lib/src/main/java/com/signnow/library/facades/ServiceProvider.java
index 0e979cb..2edf25c 100644
--- a/client-lib/src/main/java/com/signnow/library/facades/ServiceProvider.java
+++ b/client-lib/src/main/java/com/signnow/library/facades/ServiceProvider.java
@@ -6,4 +6,6 @@ public interface ServiceProvider {
Templates templatesService();
DocumentGroups documentGroupsService();
+
+ Webhooks webhooksService();
}
diff --git a/client-lib/src/main/java/com/signnow/library/facades/Webhooks.java b/client-lib/src/main/java/com/signnow/library/facades/Webhooks.java
new file mode 100644
index 0000000..b790b4e
--- /dev/null
+++ b/client-lib/src/main/java/com/signnow/library/facades/Webhooks.java
@@ -0,0 +1,10 @@
+package com.signnow.library.facades;
+
+import com.signnow.library.exceptions.SNException;
+
+import java.util.Map;
+
+public interface Webhooks {
+ void add(String event, String entityId, String callback, Map headers) throws SNException;
+ void delete(String webhookId) throws SNException;
+}
diff --git a/client-lib/src/main/java/com/signnow/library/services/DocumentGroupsService.java b/client-lib/src/main/java/com/signnow/library/services/DocumentGroupsService.java
index 971c601..c50c71f 100644
--- a/client-lib/src/main/java/com/signnow/library/services/DocumentGroupsService.java
+++ b/client-lib/src/main/java/com/signnow/library/services/DocumentGroupsService.java
@@ -91,4 +91,43 @@ public void resendInvites(String documentGroupId, String inviteId, String email)
GenericId.class
);
}
+
+ @Override
+ public DocumentGroup.EmbeddedGroupInviteResponse createDocumentGroupEmbeddedInvite(String documentGroupId, DocumentGroup.EmbeddedGroupInviteRequest request) throws SNException {
+ return client.post(
+ "/v2/document-groups/{document_group_id}/embedded-invites",
+ Collections.singletonMap("document_group_id", documentGroupId),
+ request,
+ DocumentGroup.EmbeddedGroupInviteResponse.class
+ );
+ }
+
+ @Override
+ public String getDocumentGroupEmbeddedInviteLink(String documentGroupId, String embeddedInviteId, DocumentGroup.EmbeddedGroupInviteLinkRequest request) throws SNException {
+ Map params = new HashMap<>();
+ params.put("document_group_id", documentGroupId);
+ params.put("embedded_invite_id", embeddedInviteId);
+ DocumentGroup.EmbeddedGroupInviteLinkResponse response = client.post(
+ "/v2/document-groups/{document_group_id}/embedded-invites/{embedded_invite_id}/link",
+ params, request, DocumentGroup.EmbeddedGroupInviteLinkResponse.class);
+ return response.getLink();
+ }
+
+ @Override
+ public DocumentGroup.DocumentGroupV2Response getDocumentGroupV2(String documentGroupId) throws SNException {
+ return client.get(
+ "/v2/document-groups/{document_group_id}",
+ Collections.singletonMap("document_group_id", documentGroupId),
+ DocumentGroup.DocumentGroupV2Response.class
+ );
+ }
+
+ @Override
+ public void deleteDocumentGroupEmbeddedInvite(String documentGroupId) throws SNException {
+ client.delete(
+ "/v2/document-groups/{document_group_id}/embedded-invites",
+ Collections.singletonMap("document_group_id", documentGroupId),
+ String.class
+ );
+ }
}
diff --git a/client-lib/src/main/java/com/signnow/library/services/DocumentsService.java b/client-lib/src/main/java/com/signnow/library/services/DocumentsService.java
index 551f64f..8465a2a 100644
--- a/client-lib/src/main/java/com/signnow/library/services/DocumentsService.java
+++ b/client-lib/src/main/java/com/signnow/library/services/DocumentsService.java
@@ -14,7 +14,9 @@
import javax.ws.rs.core.Response;
import java.io.InputStream;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class DocumentsService extends ApiService implements Documents {
public DocumentsService(SNClient client) {
@@ -72,6 +74,45 @@ public void sendDocumentSignInvite(String documentId, Document.SigningInviteWith
);
}
+ @Override
+ public Document.EmbeddedSigningInviteResponse createDocumentEmbeddedSignInvite(String documentId, Document.EmbeddedSigningInviteRequest request) throws SNException {
+ return client.post(
+ "/v2/documents/{document_id}/embedded-invites",
+ Collections.singletonMap("document_id", documentId),
+ request,
+ Document.EmbeddedSigningInviteResponse.class);
+ }
+
+ @Override
+ public String getDocumentEmbeddedSignInviteLink(String documentId, String inviteId, Document.EmbeddedInviteLinkRequest request) throws SNException {
+ Map params = new HashMap<>();
+ params.put("document_id", documentId);
+ params.put("fieldInviteUniqueId", inviteId);
+ Document.EmbeddedInviteLinkResponse response = client.post(
+ "/v2/documents/{document_id}/embedded-invites/{fieldInviteUniqueId}/link",
+ params, request, Document.EmbeddedInviteLinkResponse.class);
+ return response.getLink();
+ }
+
+ @Override
+ public void deleteDocumentEmbeddedSignInvite(String documentId) throws SNException {
+ client.delete(
+ "/v2/documents/{document_id}/embedded-invites",
+ Collections.singletonMap("document_id", documentId),
+ String.class
+ );
+ }
+
+ @Override
+ public void prefillText(String documentId, List request) throws SNException {
+ client.put(
+ "/v2/documents/{documentId}/prefill-texts",
+ Collections.singletonMap("documentId", documentId),
+ new Document.PrefillTextRequest(request),
+ String.class
+ );
+ }
+
@Override
public void updateDocumentFields(String documentId, List request) throws SNException {
client.put(
@@ -109,4 +150,5 @@ public String getDownloadLink(String documentId) throws SNException {
Document.DocumentDownloadLink.class
).link;
}
+
}
diff --git a/client-lib/src/main/java/com/signnow/library/services/WebhookService.java b/client-lib/src/main/java/com/signnow/library/services/WebhookService.java
new file mode 100644
index 0000000..4931ae2
--- /dev/null
+++ b/client-lib/src/main/java/com/signnow/library/services/WebhookService.java
@@ -0,0 +1,35 @@
+package com.signnow.library.services;
+
+import com.signnow.library.SNClient;
+import com.signnow.library.dto.Webhook;
+import com.signnow.library.exceptions.SNException;
+import com.signnow.library.facades.Webhooks;
+
+import java.util.Collections;
+import java.util.Map;
+
+public class WebhookService extends ApiService implements Webhooks {
+
+ public WebhookService(SNClient client) {
+ super(client);
+ }
+
+ @Override
+ public void add(String event, String entityId, String callback, Map headers) throws SNException {
+ client.post(
+ "/api/v2/events",
+ null,
+ new Webhook.CreateRequest(event, entityId, callback, headers),
+ String.class
+ );
+ }
+
+ @Override
+ public void delete(String eventSubscriptionId) throws SNException {
+ client.delete(
+ "/api/v2/events/{eventSubscriptionId}",
+ Collections.singletonMap("eventSubscriptionId", eventSubscriptionId),
+ String.class
+ );
+ }
+}
diff --git a/client-lib/src/test/java/com/signnow/library/dto/DocumentGroupEmbeddedInviteSerializationTest.java b/client-lib/src/test/java/com/signnow/library/dto/DocumentGroupEmbeddedInviteSerializationTest.java
new file mode 100644
index 0000000..66e01e6
--- /dev/null
+++ b/client-lib/src/test/java/com/signnow/library/dto/DocumentGroupEmbeddedInviteSerializationTest.java
@@ -0,0 +1,110 @@
+package com.signnow.library.dto;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Wire-format tests for the embedded group invite DTOs: request bodies must carry
+ * the exact snake_case keys with nulls omitted, responses must tolerate unknown keys
+ * (the mapper below fails on unknown properties, like the client's Jackson provider).
+ */
+class DocumentGroupEmbeddedInviteSerializationTest {
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ @Test
+ void createRequestSerializesExactKeys() throws Exception {
+ DocumentGroup.EmbeddedGroupInviteDocument document =
+ new DocumentGroup.EmbeddedGroupInviteDocument("doc123", "Signer 1");
+ DocumentGroup.EmbeddedGroupInviteSigner signer =
+ new DocumentGroup.EmbeddedGroupInviteSigner("signer@example.com", document);
+ signer.firstName = "John";
+ signer.lastName = "Doe";
+ signer.language = "en";
+ DocumentGroup.EmbeddedGroupInviteRequest request = new DocumentGroup.EmbeddedGroupInviteRequest(
+ new DocumentGroup.EmbeddedGroupInviteStep(1, signer));
+
+ JsonNode actual = mapper.readTree(mapper.writeValueAsString(request));
+ JsonNode expected = mapper.readTree("{"
+ + "\"invites\":[{\"order\":1,\"signers\":[{"
+ + "\"email\":\"signer@example.com\",\"auth_method\":\"none\","
+ + "\"first_name\":\"John\",\"last_name\":\"Doe\",\"language\":\"en\","
+ + "\"documents\":[{\"id\":\"doc123\",\"action\":\"sign\",\"role\":\"Signer 1\"}]"
+ + "}]}],"
+ + "\"sign_as_merged\":true}");
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ void createRequestOmitsNullOptionalFields() throws Exception {
+ DocumentGroup.EmbeddedGroupInviteRequest request = new DocumentGroup.EmbeddedGroupInviteRequest(
+ new DocumentGroup.EmbeddedGroupInviteStep(1,
+ new DocumentGroup.EmbeddedGroupInviteSigner("signer@example.com",
+ new DocumentGroup.EmbeddedGroupInviteDocument("doc123", "Signer 1"))));
+
+ JsonNode signer = mapper.readTree(mapper.writeValueAsString(request))
+ .get("invites").get(0).get("signers").get(0);
+
+ assertFalse(signer.has("first_name"));
+ assertFalse(signer.has("last_name"));
+ assertFalse(signer.has("language"));
+ }
+
+ @Test
+ void linkRequestSerializesExactKeys() throws Exception {
+ DocumentGroup.EmbeddedGroupInviteLinkRequest request =
+ new DocumentGroup.EmbeddedGroupInviteLinkRequest("signer@example.com");
+
+ JsonNode actual = mapper.readTree(mapper.writeValueAsString(request));
+ JsonNode expected = mapper.readTree(
+ "{\"email\":\"signer@example.com\",\"auth_method\":\"none\",\"link_expiration\":45}");
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ void createResponseToleratesUnknownKeys() throws Exception {
+ DocumentGroup.EmbeddedGroupInviteResponse response = mapper.readValue(
+ "{\"data\":{\"id\":\"invite123\",\"unexpected\":\"x\"},\"extra\":1}",
+ DocumentGroup.EmbeddedGroupInviteResponse.class);
+
+ assertEquals("invite123", response.getId());
+ }
+
+ @Test
+ void linkResponseToleratesUnknownKeys() throws Exception {
+ DocumentGroup.EmbeddedGroupInviteLinkResponse response = mapper.readValue(
+ "{\"data\":{\"link\":\"https://example.com/sign\"},\"extra\":\"x\"}",
+ DocumentGroup.EmbeddedGroupInviteLinkResponse.class);
+
+ assertEquals("https://example.com/sign", response.getLink());
+ }
+
+ @Test
+ void groupInfoV2ResponseToleratesUnknownKeys() throws Exception {
+ DocumentGroup.DocumentGroupV2Response response = mapper.readValue("{\"data\":{"
+ + "\"id\":\"group123\",\"name\":\"Bundle\",\"created\":1712233114,"
+ + "\"invite_id\":null,\"pending_step_id\":\"step1\",\"state\":\"pending\","
+ + "\"last_invite_id\":\"inv1\",\"folder_id\":\"f1\","
+ + "\"documents\":[{\"id\":\"doc1\",\"roles\":[\"Signer 1\"],"
+ + "\"document_name\":\"Contract\",\"updated\":1712233114,"
+ + "\"field_invites\":[{\"id\":\"fi1\",\"status\":\"pending\","
+ + "\"signer_email\":\"signer@example.com\",\"created\":1712233114}]}],"
+ + "\"freeform_invite\":{\"id\":null,\"last_id\":null}}}",
+ DocumentGroup.DocumentGroupV2Response.class);
+
+ assertEquals("group123", response.data.id);
+ assertNull(response.data.inviteId);
+ assertEquals("pending", response.data.state);
+ assertEquals("doc1", response.data.documents.get(0).id);
+ assertTrue(response.data.documents.get(0).roles.contains("Signer 1"));
+ assertEquals("pending", response.data.documents.get(0).fieldInvites.get(0).status);
+ assertEquals("signer@example.com", response.data.documents.get(0).fieldInvites.get(0).signerEmail);
+ }
+}
diff --git a/client-lib/src/test/java/com/signnow/library/services/DocumentGroupsServiceTest.java b/client-lib/src/test/java/com/signnow/library/services/DocumentGroupsServiceTest.java
index ab4b9aa..4b8c9f0 100644
--- a/client-lib/src/test/java/com/signnow/library/services/DocumentGroupsServiceTest.java
+++ b/client-lib/src/test/java/com/signnow/library/services/DocumentGroupsServiceTest.java
@@ -118,6 +118,68 @@ void createDocumentGroupInvite() throws SNException {
, eq(GenericId.class));
}
+ @Test
+ void createDocumentGroupEmbeddedInvite() throws SNException {
+ DocumentGroup.EmbeddedGroupInviteRequest requestMock = mock(DocumentGroup.EmbeddedGroupInviteRequest.class);
+ final String path = "/v2/document-groups/{document_group_id}/embedded-invites";
+ when(clientMock.post(eq(path)
+ , anyMap()
+ , eq(requestMock)
+ , eq(DocumentGroup.EmbeddedGroupInviteResponse.class))
+ ).thenReturn(new DocumentGroup.EmbeddedGroupInviteResponse());
+
+ service.createDocumentGroupEmbeddedInvite("1", requestMock);
+
+ verify(clientMock, times(1)).post(
+ eq(path)
+ , anyMap()
+ , eq(requestMock)
+ , eq(DocumentGroup.EmbeddedGroupInviteResponse.class));
+ }
+
+ @Test
+ void getDocumentGroupEmbeddedInviteLink() throws SNException {
+ DocumentGroup.EmbeddedGroupInviteLinkRequest request = new DocumentGroup.EmbeddedGroupInviteLinkRequest("signer@example.com");
+ DocumentGroup.EmbeddedGroupInviteLinkResponse response = new DocumentGroup.EmbeddedGroupInviteLinkResponse();
+ response.data.put("link", "https://link");
+ final String path = "/v2/document-groups/{document_group_id}/embedded-invites/{embedded_invite_id}/link";
+ when(clientMock.post(eq(path)
+ , anyMap()
+ , eq(request)
+ , eq(DocumentGroup.EmbeddedGroupInviteLinkResponse.class))
+ ).thenReturn(response);
+
+ String link = service.getDocumentGroupEmbeddedInviteLink("1", "2", request);
+
+ assertEquals("https://link", link);
+ verify(clientMock, times(1)).post(
+ eq(path)
+ , anyMap()
+ , eq(request)
+ , eq(DocumentGroup.EmbeddedGroupInviteLinkResponse.class));
+ }
+
+ @Test
+ void getDocumentGroupV2() throws SNException {
+ final String path = "/v2/document-groups/{document_group_id}";
+ when(clientMock.get(eq(path), anyMap(), eq(DocumentGroup.DocumentGroupV2Response.class)))
+ .thenReturn(new DocumentGroup.DocumentGroupV2Response());
+
+ service.getDocumentGroupV2("1");
+
+ verify(clientMock, times(1)).get(eq(path), anyMap(), eq(DocumentGroup.DocumentGroupV2Response.class));
+ }
+
+ @Test
+ void deleteDocumentGroupEmbeddedInvite() throws SNException {
+ final String path = "/v2/document-groups/{document_group_id}/embedded-invites";
+ when(clientMock.delete(eq(path), anyMap(), eq(String.class))).thenReturn("{}");
+
+ service.deleteDocumentGroupEmbeddedInvite("1");
+
+ verify(clientMock, times(1)).delete(eq(path), anyMap(), eq(String.class));
+ }
+
@Test
void resendInvites() throws SNException {
final String path = "/documentgroup/{documentGroupId}/groupinvite/{inviteId}/resendinvites";
diff --git a/client-lib/src/test/java/com/signnow/library/services/DocumentsServiceTest.java b/client-lib/src/test/java/com/signnow/library/services/DocumentsServiceTest.java
index 0ba6b08..5247fab 100644
--- a/client-lib/src/test/java/com/signnow/library/services/DocumentsServiceTest.java
+++ b/client-lib/src/test/java/com/signnow/library/services/DocumentsServiceTest.java
@@ -146,6 +146,15 @@ void deleteDocument() throws SNException {
verify(clientMock, times(1)).delete(anyString(), anyMap(), any());
}
+ @Test
+ void deleteDocumentEmbeddedSignInvite() throws SNException {
+ when(clientMock.delete(anyString(), anyMap(), any())).thenReturn(null);
+
+ service.deleteDocumentEmbeddedSignInvite("1");
+
+ verify(clientMock, times(1)).delete(anyString(), anyMap(), any());
+ }
+
@Test
void getDownloadLink() throws SNException {
when(clientMock.post(anyString()