@verdocs/js-sdk 6.2.0-beta.4 → 6.2.0-beta.6

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/dist/index.js CHANGED
@@ -1770,1721 +1770,1818 @@ const getEnvelopes = (endpoint, params) => endpoint.api //
1770
1770
  const getEnvelopesZip = (endpoint, envelope_ids) => endpoint.api //
1771
1771
  .get(`/v2/envelopes/zip/${envelope_ids.join(',')}`, { responseType: 'blob', timeout: 120000 });
1772
1772
 
1773
+ const canPerformTemplateAction = (profile, action, template) => {
1774
+ if (!template && !action.includes('create')) {
1775
+ return { canPerform: false, message: 'Missing required template object' };
1776
+ }
1777
+ // We use BOGUS here to force the option-chain in things like template?.profile_id to NOT match profile?.profile_id because if both
1778
+ // were undefined, they would actually match.
1779
+ const profile_id = profile?.id || 'BOGUS';
1780
+ const organization_id = profile?.organization_id || 'BOGUS';
1781
+ const isCreator = template?.profile_id === profile_id;
1782
+ const isSameOrg = template?.organization_id === organization_id;
1783
+ const isPersonal = template?.is_personal ?? false;
1784
+ const isPublic = template?.is_public ?? false;
1785
+ const permissionsRequired = [];
1786
+ switch (action) {
1787
+ case 'create_personal':
1788
+ permissionsRequired.push('template:creator:create:personal');
1789
+ break;
1790
+ case 'create_org':
1791
+ permissionsRequired.push('template:creator:create:org');
1792
+ break;
1793
+ case 'create_public':
1794
+ permissionsRequired.push('template:creator:create:public');
1795
+ break;
1796
+ case 'read':
1797
+ if (!isCreator) {
1798
+ if ((!isPersonal && isSameOrg) || !isPublic) {
1799
+ permissionsRequired.push('template:member:read');
1800
+ }
1801
+ }
1802
+ break;
1803
+ case 'write':
1804
+ if (!isCreator) {
1805
+ permissionsRequired.push('template:member:read');
1806
+ permissionsRequired.push('template:member:write');
1807
+ }
1808
+ break;
1809
+ case 'change_visibility_personal':
1810
+ if (isCreator) {
1811
+ permissionsRequired.push('template:creator:create:personal');
1812
+ }
1813
+ else {
1814
+ permissionsRequired.push('template:member:visibility');
1815
+ }
1816
+ break;
1817
+ case 'change_visibility_org':
1818
+ if (isCreator) {
1819
+ permissionsRequired.push('template:creator:create:org');
1820
+ }
1821
+ else {
1822
+ permissionsRequired.push('template:member:visibility');
1823
+ }
1824
+ break;
1825
+ case 'change_visibility_public':
1826
+ if (isCreator) {
1827
+ permissionsRequired.push('template:creator:create:public');
1828
+ permissionsRequired.push('template:creator:visibility');
1829
+ }
1830
+ else {
1831
+ permissionsRequired.push('template:member:visibility');
1832
+ }
1833
+ break;
1834
+ case 'delete':
1835
+ if (isCreator) {
1836
+ permissionsRequired.push('template:creator:delete');
1837
+ }
1838
+ else {
1839
+ permissionsRequired.push('template:member:delete');
1840
+ }
1841
+ break;
1842
+ default:
1843
+ return { canPerform: false, message: 'Action is not defined' };
1844
+ }
1845
+ if (hasRequiredPermissions(profile, permissionsRequired)) {
1846
+ return { canPerform: true, message: '' };
1847
+ }
1848
+ return { canPerform: false, message: `Insufficient access to perform '${action}'. Needed permissions: ${permissionsRequired.toString()}` };
1849
+ };
1850
+ const hasRequiredPermissions = (profile, permissions) => permissions.every((perm) => (profile?.permissions || []).includes(perm));
1851
+
1852
+ /**
1853
+ * Add a field to a template.
1854
+ *
1855
+ * ```typescript
1856
+ * import {createField} from '@verdocs/js-sdk/Templates';
1857
+ *
1858
+ * await createField((VerdocsEndpoint.getDefault(), template_id, { ... });
1859
+ * ```
1860
+ *
1861
+ * @group Fields
1862
+ * @api POST /v2/fields/:template_id Add a field to a template
1863
+ * @apiBody string name Name for the new field. Field names must be unique within a template. Although special characters are allowed, they must be URL-encoded in subsequent requests, so it is recommended to use only alphanumeric characters and hyphens if possible.
1864
+ * @apiBody string role_name Role to assign to the field. Role names must be valid, so it is recommended to create roles before fields.
1865
+ * @apiBody string document_id ID of the document upon which to place the field.
1866
+ * @apiBody string(enum: 'signature' | 'initial' | 'checkbox' | 'radio' | 'textbox' | 'timestamp' | 'date' | 'dropdown' | 'textarea' | 'attachment' | 'payment') type Type of field to create
1867
+ * @apiBody boolean(default: false) required Whether the field is required
1868
+ * @apiBody integer(min: 0) page 0-based page number upon which to place the field
1869
+ * @apiBody integer(min: 0) x X position for the field (left to right)
1870
+ * @apiBody integer(min: 0) y Y position for the field (_bottom to top!_)
1871
+ * @apiBody string label? Optional label to display above the field
1872
+ * @apiBody integer(min: 50) width? Width of the field. Note that all fields have built-in defaults, and it is recommended that this only be set on text fields.
1873
+ * @apiBody integer(min: 15) height? Height of the field. Note that all fields have built-in defaults, and it is recommended that this only be set on text fields.
1874
+ * @apiBody string placeholder? Optional placeholder to display in text fields
1875
+ * @apiBody string group? For fields that support grouping (radio buttons and check boxes) the value selected will be stored under this name
1876
+ * @apiBody array(items:IDropdownOption) options? For dropdown fields, the options to display
1877
+ * @apiBody string value? Optional default value to set on the field
1878
+ * @apiSuccess ITemplateField . Template field
1879
+ */
1880
+ const createField = (endpoint, templateId, params) => endpoint.api //
1881
+ .post(`/v2/fields/${templateId}`, params)
1882
+ .then((r) => r.data);
1883
+ /**
1884
+ * Update a template field.
1885
+ *
1886
+ * ```typescript
1887
+ * import {updateField} from '@verdocs/js-sdk/Templates';
1888
+ *
1889
+ * await updateField((VerdocsEndpoint.getDefault(), template_id, field_name, { ... });
1890
+ * ```
1891
+ *
1892
+ * @group Fields
1893
+ * @api PATCH /v2/fields/:template_id/:field_name Update a field. See createField for additional details on the supported parameters.
1894
+ * @apiBody string name? Rename the field. Note that template field names must be unique within a template.
1895
+ * @apiBody string role_name Role to assign to the field.
1896
+ * @apiBody string document_id ID of the document upon which to place the field.
1897
+ * @apiBody string(enum: 'signature' | 'initial' | 'checkbox' | 'radio' | 'textbox' | 'timestamp' | 'date' | 'dropdown' | 'textarea' | 'attachment' | 'payment') type? Change the field type. Note that while this is technically allowed, fields have different behaviors, validators, default sizes, etc. It is usually easier to add a new field and delete the old one.
1898
+ * @apiBody boolean(default: false) required? Whether the field is required
1899
+ * @apiBody integer(min: 0) page? 0-based page number upon which to place the field
1900
+ * @apiBody integer(min: 0) x? X position for the field (left to right)
1901
+ * @apiBody integer(min: 0) y? Y position for the field (_bottom to top!_)
1902
+ * @apiBody string label? Optional label to display above the field
1903
+ * @apiBody integer(min: 50) width? Width of the field. Note that all fields have built-in defaults, and it is recommended that this only be set on text fields.
1904
+ * @apiBody integer(min: 15) height? Height of the field. Note that all fields have built-in defaults, and it is recommended that this only be set on text fields.
1905
+ * @apiBody string placeholder? Optional placeholder to display in text fields
1906
+ * @apiBody string group? For fields that support grouping (radio buttons and check boxes) the value selected will be stored under this name
1907
+ * @apiBody array(items:IDropdownOption) options? For dropdown fields, the options to display
1908
+ * @apiBody string value? Optional default value to set on the field
1909
+ * @apiSuccess ITemplateField . Updated template field
1910
+ */
1911
+ const updateField = (endpoint, templateId, name, params) => endpoint.api //
1912
+ .patch(`/v2/fields/${templateId}/${encodeURIComponent(name)}`, params)
1913
+ .then((r) => r.data);
1914
+ /**
1915
+ * Remove a field from a template.
1916
+ *
1917
+ * ```typescript
1918
+ * import {deleteField} from '@verdocs/js-sdk/Templates';
1919
+ *
1920
+ * await deleteField((VerdocsEndpoint.getDefault(), template_id, field_name);
1921
+ * ```
1922
+ *
1923
+ * @group Fields
1924
+ * @api DELETE /v2/fields/:template_id/:field_name Delete a field
1925
+ * @apiSuccess string . Success
1926
+ */
1927
+ const deleteField = (endpoint, templateId, name) => endpoint.api //
1928
+ .delete(`/v2/fields/${templateId}/${encodeURIComponent(name)}`)
1929
+ .then((r) => r.data);
1930
+
1931
+ /**
1932
+ * A map of the permissions each role confers.
1933
+ */
1934
+ const RolePermissions = {
1935
+ owner: [
1936
+ 'template:creator:create:public',
1937
+ 'template:creator:create:org',
1938
+ 'template:creator:create:personal',
1939
+ 'template:creator:delete',
1940
+ 'template:creator:visibility',
1941
+ 'template:member:read',
1942
+ 'template:member:write',
1943
+ 'template:member:delete',
1944
+ 'template:member:visibility',
1945
+ 'owner:add',
1946
+ 'owner:remove',
1947
+ 'admin:add',
1948
+ 'admin:remove',
1949
+ 'member:view',
1950
+ 'member:add',
1951
+ 'member:remove',
1952
+ 'org:create',
1953
+ 'org:view',
1954
+ 'org:update',
1955
+ 'org:delete',
1956
+ 'org:transfer',
1957
+ 'org:list',
1958
+ 'envelope:create',
1959
+ 'envelope:cancel',
1960
+ 'envelope:view',
1961
+ ],
1962
+ admin: [
1963
+ 'template:creator:create:public',
1964
+ 'template:creator:create:org',
1965
+ 'template:creator:create:personal',
1966
+ 'template:creator:delete',
1967
+ 'template:creator:visibility',
1968
+ 'template:member:read',
1969
+ 'template:member:write',
1970
+ 'template:member:delete',
1971
+ 'template:member:visibility',
1972
+ 'admin:add',
1973
+ 'admin:remove',
1974
+ 'member:view',
1975
+ 'member:add',
1976
+ 'member:remove',
1977
+ 'org:create',
1978
+ 'org:view',
1979
+ 'org:update',
1980
+ 'org:list',
1981
+ 'envelope:create',
1982
+ 'envelope:cancel',
1983
+ 'envelope:view',
1984
+ ],
1985
+ member: [
1986
+ 'template:creator:create:public',
1987
+ 'template:creator:create:org',
1988
+ 'template:creator:create:personal',
1989
+ 'template:creator:delete',
1990
+ 'template:creator:visibility',
1991
+ 'template:member:read',
1992
+ 'template:member:write',
1993
+ 'template:member:delete',
1994
+ 'member:view',
1995
+ 'org:create',
1996
+ 'org:view',
1997
+ 'org:list',
1998
+ 'envelope:create',
1999
+ 'envelope:cancel',
2000
+ 'envelope:view',
2001
+ ],
2002
+ basic_user: ['template:member:read', 'member:view', 'org:view', 'org:list'],
2003
+ contact: ['org:view', 'org:list', 'org:create'],
2004
+ };
2005
+ /**
2006
+ * Confirm whether the user has all of the specified permissions.
2007
+ */
2008
+ const userHasPermissions = (profile, permissions) => {
2009
+ // No need to de-dupe here, we're just checking present-at-least-once set membership.
2010
+ const netPermissions = [...(profile?.permissions || [])];
2011
+ (profile?.roles || []).forEach((role) => {
2012
+ netPermissions.push(...(RolePermissions[role] || []));
2013
+ });
2014
+ (profile?.group_profiles || []).forEach((groupProfile) => {
2015
+ netPermissions.push(...(groupProfile.group?.permissions || []));
2016
+ });
2017
+ return permissions.every((perm) => netPermissions.includes(perm));
2018
+ };
2019
+
2020
+ /**
2021
+ * Various helpers to identify available operations for a template by a user.
2022
+ *
2023
+ * @module
2024
+ */
2025
+ /**
2026
+ * Check to see if the user created the template.
2027
+ */
2028
+ const userIsTemplateCreator = (profile, template) => profile && template && profile.id === template.profile_id;
2029
+ /**
2030
+ * Check to see if a template is "shared" with the user.
2031
+ */
2032
+ const userHasSharedTemplate = (profile, template) => profile && template && !template.is_personal && profile.organization_id === template.organization_id;
2033
+ /**
2034
+ * Check to see if the user can create a personal/private template.
2035
+ */
2036
+ const userCanCreatePersonalTemplate = (profile) => userHasPermissions(profile, ['template:creator:create:personal']);
2037
+ /**
2038
+ * Check to see if the user can create an org-shared template.
2039
+ */
2040
+ const userCanCreateOrgTemplate = (profile) => userHasPermissions(profile, ['template:creator:create:org']);
1773
2041
  /**
1774
- * Create an initials block. In a typical signing workflow, the user is asked at the beginning of the process to
1775
- * "adopt" an initials block to be used for all initials fields in the document. Thus, this is typically called
1776
- * one time to create and store an initials block. Thereafter, the ID of the initials block may be re-used for each
1777
- * initials field to be "stamped" by the user.
1778
- *
1779
- * Note: Both "guest" signers and authenticated users can create initials blocks. Guest signers
1780
- * typically only ever have one, tied to that session. But authenticated users can create more than
1781
- * one, and can use them interchangeably.
1782
- *
1783
- * @group Signatures and Initials
1784
- * @api POST /v2/profiles/initials Create Initial Block
1785
- * @apiBody string initial Blob containing initials image to store.
1786
- * @apiSuccess IInitial . The newly-created initial block.
2042
+ * Check to see if the user can create a public template.
1787
2043
  */
1788
- const createInitials = (endpoint, name, initials) => {
1789
- const data = new FormData();
1790
- data.append('initial', initials, name);
1791
- return endpoint.api //
1792
- .post(`/v2/profiles/initials`, data)
1793
- .then((r) => r.data);
2044
+ const userCanCreatePublicTemplate = (profile) => userHasPermissions(profile, ['template:creator:create:public']);
2045
+ /**
2046
+ * Check to see if the user can read/view a template.
2047
+ */
2048
+ const userCanReadTemplate = (profile, template) => template.is_public ||
2049
+ userIsTemplateCreator(profile, template) ||
2050
+ (userHasSharedTemplate(profile, template) && userHasPermissions(profile, ['template:member:read']));
2051
+ /**
2052
+ * Check to see if the user can update a tempate.
2053
+ */
2054
+ const userCanUpdateTemplate = (profile, template) => userIsTemplateCreator(profile, template) ||
2055
+ (userHasSharedTemplate(profile, template) && userHasPermissions(profile, ['template:member:read', 'template:member:write']));
2056
+ /**
2057
+ * Check to see if the user can change whether a template is personal vs org-shared.
2058
+ */
2059
+ const userCanMakeTemplatePrivate = (profile, template) => userIsTemplateCreator(profile, template)
2060
+ ? userHasPermissions(profile, ['template:creator:create:personal'])
2061
+ : userHasPermissions(profile, ['template:member:visibility']);
2062
+ /**
2063
+ * Check to see if the user can change whether a template is personal vs org-shared.
2064
+ */
2065
+ const userCanMakeTemplateShared = (profile, template) => userIsTemplateCreator(profile, template)
2066
+ ? userHasPermissions(profile, ['template:creator:create:org'])
2067
+ : userHasPermissions(profile, ['template:member:visibility']);
2068
+ /**
2069
+ * Check to see if the user can change whether a template is personal vs org-shared.
2070
+ */
2071
+ const userCanMakeTemplatePublic = (profile, template) => userIsTemplateCreator(profile, template)
2072
+ ? userHasPermissions(profile, ['template:creator:create:public'])
2073
+ : userHasPermissions(profile, ['template:member:visibility']);
2074
+ /**
2075
+ * Check to see if the user can change whether a template is personal vs org-shared.
2076
+ */
2077
+ const userCanChangeOrgVisibility = (profile, template) => userIsTemplateCreator(profile, template) && userHasPermissions(profile, ['template:creator:create:personal']);
2078
+ /**
2079
+ * Check to see if the user can change whether a template is personal vs org-shared.
2080
+ */
2081
+ const userCanDeleteTemplate = (profile, template) => userIsTemplateCreator(profile, template)
2082
+ ? userHasPermissions(profile, ['template:creator:delete'])
2083
+ : userHasPermissions(profile, ['template:member:delete']);
2084
+ /**
2085
+ * Confirm whether the user can create an envelope using the specified template.
2086
+ */
2087
+ const userCanSendTemplate = (profile, template) => {
2088
+ switch (template.visibility) {
2089
+ case 'private':
2090
+ return userIsTemplateCreator(profile, template);
2091
+ case 'shared':
2092
+ return userIsTemplateCreator(profile, template) || template.organization_id === profile?.organization_id;
2093
+ case 'public':
2094
+ return true;
2095
+ }
1794
2096
  };
1795
-
1796
2097
  /**
1797
- * Get the current KBA status. Note that this may only be called by the recipient and requires a
1798
- * valid signing session to proceed. Although the Recipient object itself contains indications of
1799
- * whether KBA is required, it will not contain the current status of the process. If
1800
- * `recipient.auth_methods` is set (not empty), and `recipient.kba_completed` is false, this endpoint
1801
- * should be called to determine the next KBA step required.
2098
+ * Confirm whether the user can create a new template.
1802
2099
  */
1803
- const getKbaStep = (endpoint, envelope_id, role_name) => endpoint.api //
1804
- .get(`/v2/kba/${envelope_id}/${encodeURIComponent(role_name)}`)
1805
- .then((r) => r.data);
2100
+ const userCanCreateTemplate = (profile) => userCanCreatePersonalTemplate(profile) || userCanCreateOrgTemplate(profile) || userCanCreatePublicTemplate(profile);
1806
2101
  /**
1807
- * Submit a response to a KBA PIN challenge.
2102
+ * Check to see if the user can "build" the template (use the field builder). The user must have write access to the
2103
+ * template, and the template must have at least one signer role.
1808
2104
  */
1809
- const submitKbaPin = (endpoint, envelope_id, role_name, pin) => endpoint.api //
1810
- .post(`/v2/kba/pin`, { envelope_id, role_name, pin })
1811
- .then((r) => r.data);
2105
+ const userCanBuildTemplate = (profile, template) => userCanUpdateTemplate(profile, template) && (template.roles || []).filter((role) => role.type === 'signer').length > 0;
2106
+ const getFieldsForRole = (template, role_name) => (template.fields || []).filter((field) => field.role_name === role_name);
1812
2107
  /**
1813
- * Submit an identity response to a KBA challenge.
2108
+ * Check to see if the user can preview the template. The user must have read access to the template, the template must
2109
+ * have at least one signer, and every signer must have at least one field.
1814
2110
  */
1815
- const submitKbaIdentity = (endpoint, envelope_id, role_name, identity) => endpoint.api //
1816
- .post(`/v2/kba/identity`, { envelope_id, role_name, identity })
1817
- .then((r) => r.data);
2111
+ const userCanPreviewTemplate = (profile, template) => {
2112
+ const hasPermission = userCanReadTemplate(profile, template);
2113
+ const signers = (template.roles || []).filter((role) => role.type === 'signer');
2114
+ return hasPermission && signers.length > 0 && signers.every((signer) => getFieldsForRole(template, signer.name).length > 0);
2115
+ };
2116
+
1818
2117
  /**
1819
- * Submit an identity response to a KBA challenge. Answers should be submitted in the same order as
1820
- * the challenges were listed in `IRecipientKbaStepChallenge.questions`.
2118
+ * A "role" is an individual participant in a signing flow, such as a signer or CC contact.
2119
+ * A role is a placeholder that will eventually become a named recipient. For example, "Tenant 1"
2120
+ * might be replaced with "John Smith" when the document is sent out for signature.
2121
+ *
2122
+ * Role names must be unique within a template, e.g. 'Recipient 1'. They may contain any [a-zA-Z0-9_- ]
2123
+ * characters, although it is recommended to keep them simple and human-readable, and to avoid
2124
+ * spaces (although they are allowed). If spaces are used in role names, be sure to URL-encode them
2125
+ * when calling endpoints like `updateRole()` e.g. 'Recipient%201'.
2126
+ *
2127
+ * NOTE: Roles are always enumerated under Template objects, so there are no "list" or "get" endpoints
2128
+ * for them. To get a template's latest role list, simply call `getTemplate()`.
2129
+ *
2130
+ * @module
1821
2131
  */
1822
- const submitKbaChallengeResponse = (endpoint, envelope_id, role_name, responses) => endpoint.api //
1823
- .post(`/v2/kba/response`, { envelope_id, role_name, responses })
2132
+ /**
2133
+ * Create a role.
2134
+ *
2135
+ * ```typescript
2136
+ * import {createTemplateRole} from '@verdocs/js-sdk';
2137
+ *
2138
+ * const role = await createTemplateRole(VerdocsEndpoint.getDefault(), template_id, params...);
2139
+ * ```
2140
+ *
2141
+ * @group Roles
2142
+ * @api POST /v2/roles/:template_id Add a role to a template
2143
+ * @apiBody string name Name for the new role. Must be unique within the template. May include spaces, but later calls must URL-encode any references to this role, so it is recomended that special characters be avoided.
2144
+ * @apiBody string(enum:'signer' | 'cc' | 'approver') type Type of role to create. Signers act on documents by filling and signing fields. CC recipients receive a copy but do not act on the document. Approvers control the final submission of a document, but do not have fields of their own to fill out.
2145
+ * @apiBody string full_name? Default full name for the role. May be completed/overridden later, when envelopes are made from the template.
2146
+ * @apiBody string email? Default email address for the role. May be completed/overridden later, when envelopes are made from the template.
2147
+ * @apiBody string phone? Default (SMS-capable) phone number for the role. May be completed/overridden later, when envelopes are made from the template.
2148
+ * @apiBody string message? Optional message to include in email and SMS signing invitations.
2149
+ * @apiBody integer(min: 1, default: 1) sequence? Optional 1-based sequence number for the role. Roles that share the same sequence number act in parallel, and will receive invitations at the same time.
2150
+ * @apiBody integer(min: 1, default: 1) order? Optional 1-based order number for the role. Controls the left-to-right display order of roles at the same sequence number in the UI components e.g. `<verdocs-template-roles />`.
2151
+ * @apiBody boolean delegator? If true, the role may delegate their signing responsibility to another party.
2152
+ * @apiSuccess IRole . The newly-created role
2153
+ */
2154
+ const createTemplateRole = (endpoint, template_id, params) => endpoint.api //
2155
+ .post(`/v2/roles/${template_id}`, params)
1824
2156
  .then((r) => r.data);
1825
-
1826
2157
  /**
1827
- * Agree to electronic signing dislosures.
2158
+ * Update a role.
1828
2159
  *
1829
- * @group Recipients
1830
- * @api POST /envelopes/:envelope_id/recipients/:role_name/agree Agree to e-Signing Disclosures
1831
- * @apiParam string(format:uuid) envelope_id The envelope to operate on.
1832
- * @apiParam string role_name The role to operate on.
1833
- * @apiSuccess IRecipient . The updated Recipient.
2160
+ * ```typescript
2161
+ * import {updateTemplateRole} from '@verdocs/js-sdk';
2162
+ *
2163
+ * const role = await updateTemplateRole(VerdocsEndpoint.getDefault(), template_id, name, params...);
2164
+ * ```
2165
+ *
2166
+ * @group Roles
2167
+ * @api PATCH /v2/roles/:template_id/:role_id Update a role. See createRole for additional details on the parameters available.
2168
+ * @apiBody string name? Rename the role. Note that role names must be unique within a template, so this may fail if the new name is already in use.
2169
+ * @apiBody string(enum:'signer' | 'cc' | 'approver') type? Type of role.
2170
+ * @apiBody string full_name? Default full name for the role.
2171
+ * @apiBody string email? Default email address for the role.
2172
+ * @apiBody string phone? Default (SMS-capable) phone number for the role.
2173
+ * @apiBody string message? Optional message to include in email and SMS signing invitations.
2174
+ * @apiBody integer(min: 1, default: 1) sequence? Optional 1-based sequence number for the role.
2175
+ * @apiBody integer(min: 1, default: 1) order? Optional 1-based order number for the role.
2176
+ * @apiBody boolean delegator? If true, the role may delegate their signing responsibility to another party.
2177
+ * @apiBody string(enum:'pin'|'identity'|'') kba_method? Active PIN- or Identity-based KBA for the role.
2178
+ * @apiSuccess IRole . The newly-created role
1834
2179
  */
1835
- const envelopeRecipientAgree = (endpoint, envelopeId, roleName, disclosures) => endpoint.api //
1836
- .post(`/v2/envelopes/${envelopeId}/recipients/${encodeURIComponent(roleName)}/agree`, { disclosures })
2180
+ const updateTemplateRole = (endpoint, template_id, name, params) => endpoint.api //
2181
+ .patch(`/v2/roles/${template_id}/${encodeURIComponent(name)}`, params)
1837
2182
  .then((r) => r.data);
1838
2183
  /**
1839
- * Decline electronic signing dislosures. Note that if any recipient declines, the entire envelope
1840
- * becomes non-viable and later recipients may no longer act. The creator will receive a notification
1841
- * when this occurs.
2184
+ * Delete a role.
1842
2185
  *
1843
- * @group Recipients
1844
- * @api POST /envelopes/:envelope_id/recipients/:role_name/decline Decline e-Signing Disclosures
1845
- * @apiParam string(format:uuid) envelope_id The envelope to operate on.
1846
- * @apiParam string role_name The role to adjust.
1847
- * @apiSuccess IRecipient . The updated Recipient.
2186
+ * ```typescript
2187
+ * import {deleteTemplateRole} from '@verdocs/js-sdk';
2188
+ *
2189
+ * const profiles = await deleteTemplateRole(VerdocsEndpoint.getDefault(), template_id, name);
2190
+ * ```
2191
+ *
2192
+ * @group Roles
2193
+ * @api DELETE /v2/roles/:template_id/:role_id Delete a role.
2194
+ * @apiSuccess string . Success
1848
2195
  */
1849
- const envelopeRecipientDecline = (endpoint, envelopeId, roleName) => endpoint.api //
1850
- .post(`/v2/envelopes/${envelopeId}/recipients/${encodeURIComponent(roleName)}/decline`)
2196
+ const deleteTemplateRole = (endpoint, template_id, name) => endpoint.api //
2197
+ .delete(`/v2/roles/${template_id}/${encodeURIComponent(name)}`)
1851
2198
  .then((r) => r.data);
2199
+
1852
2200
  /**
1853
- * Submit an envelope (signing is finished). Note that all fields must be valid/completed for this to succeed.
2201
+ * A Template defines how a Verdocs signing flow will be performed, including attachments, signing fields, and
2202
+ * recipients.
1854
2203
  *
1855
- * @group Recipients
1856
- * @api POST /envelopes/:envelope_id/recipients/:role_name/submit Submit envelope
1857
- * @apiParam string(format:uuid) envelope_id The envelope to operate on.
1858
- * @apiParam string role_name The role to submit.
1859
- * @apiSuccess IRecipient . The updated Recipient.
2204
+ * @module
1860
2205
  */
1861
- const envelopeRecipientSubmit = (endpoint, envelopeId, roleName) => endpoint.api //
1862
- .put(`/v2/envelopes/${envelopeId}/recipients/${roleName}/submit`)
2206
+ /**
2207
+ * Get all templates accessible by the caller, with optional filters.
2208
+ *
2209
+ * ```typescript
2210
+ * import {getTemplates} from '@verdocs/js-sdk/Templates';
2211
+ *
2212
+ * await getTemplates((VerdocsEndpoint.getDefault());
2213
+ * await getTemplates((VerdocsEndpoint.getDefault(), { is_starred: true });
2214
+ * await getTemplates((VerdocsEndpoint.getDefault(), { is_creator: true });
2215
+ * await getTemplates((VerdocsEndpoint.getDefault(), { is_organization: true });
2216
+ * ```
2217
+ *
2218
+ * @group Templates
2219
+ * @api GET /v2/templates Get Templates
2220
+ * @apiQuery string q? Find templates whose names/descriptions contain the specified query string
2221
+ * @apiQuery boolean is_starred? If true, returns only templates with at least one "star".
2222
+ * @apiQuery boolean is_creator? If true, returns only templates that the caller created.
2223
+ * @apiQuery string(enum: 'private_shared' | 'private' | 'shared' | 'public') visibility? Return only templates with the specified visibility.
2224
+ * @apiQuery string(enum: 'created_at' | 'updated_at' | 'name' | 'last_used_at' | 'counter' | 'star_counter') sort_by? Return results sorted by this criteria
2225
+ * @apiQuery boolean ascending? Set true/false to override the sort direction. Note that the default depends on `sort_by`. Date-based sorts default to descending, while name defaults to ascending.
2226
+ * @apiQuery integer(default: 20) rows? Limit the number of rows returned
2227
+ * @apiQuery integer(default: 0) page? Specify which page of results to return
2228
+ * @apiSuccess integer(format: int32) count The total number of records matching the query, helpful for pagination
2229
+ * @apiSuccess integer(format: int32) rows The number of rows returned in this response page
2230
+ * @apiSuccess integer(format: int32) page The page number of this response
2231
+ * @apiSuccess array(items: ITemplate) templates List of templates found
2232
+ */
2233
+ const getTemplates = (endpoint, params) => endpoint.api //
2234
+ .get('/v2/templates', { params })
1863
2235
  .then((r) => r.data);
1864
2236
  /**
1865
- * Begin a signing session for an Envelope. This path requires an invite code, and should generally
1866
- * be called with a NON-default Endpoint to avoid conflicting with any active user session the user
1867
- * may have. To initiate in-person signing by an authenticated user (e.g. self-signing), call
1868
- * getInPersonLink() instead. The response from that call includes both a link for direct signing
1869
- * via a Web browser as well as an in-person access_key. That access_key.key may be used here as well.
2237
+ * Get one template by its ID.
1870
2238
  *
1871
- * @group Recipients
1872
- * @api POST /v2/sign/unauth/:envelope_id/:role_name/:key Start Signing Session
1873
- * @apiParam string(format:uuid) envelope_id The envelope to operate on.
1874
- * @apiParam string role_name The role to request.
1875
- * @apiParam string key Access key generated by the envelope creator or email/SMS invite.
1876
- * @apiSuccess ISignerTokenResponse . Signing session token and envelope/recipient metadata.
2239
+ * ```typescript
2240
+ * import {getTemplate} from '@verdocs/js-sdk/Templates';
2241
+ *
2242
+ * const template = await getTemplate((VerdocsEndpoint.getDefault(), '83da3d70-7857-4392-b876-c4592a304bc9');
2243
+ * ```
2244
+ *
2245
+ * @group Templates
2246
+ * @api GET /v2/templates/:template_id Get a template. Note that the caller must have at least View access to the template.
2247
+ * @apiSuccess ITemplate . The requested template
1877
2248
  */
1878
- const startSigningSession = async (endpoint, envelope_id, role_name, key) => {
2249
+ const getTemplate = (endpoint, templateId) => {
1879
2250
  return endpoint.api //
1880
- .post(`/v2/sign/unauth/${envelope_id}/${encodeURIComponent(role_name)}/${key}`)
2251
+ .get(`/v2/templates/${templateId}`)
1881
2252
  .then((r) => {
1882
- endpoint.setToken(r.data.access_token, 'signing');
1883
- return r.data;
2253
+ const template = r.data;
2254
+ // Post-process the template to upgrade to new data fields
2255
+ if (!template.documents && template.template_documents) {
2256
+ template.documents = template.template_documents;
2257
+ }
2258
+ template.documents?.forEach((document) => {
2259
+ if (!document.order) {
2260
+ document.order = 0;
2261
+ }
2262
+ if (document.page_numbers) {
2263
+ document.pages = document.page_numbers;
2264
+ }
2265
+ });
2266
+ // Temporary upgrade from legacy app
2267
+ template.fields?.forEach((field) => {
2268
+ if (field.setting) {
2269
+ field.settings = field.setting;
2270
+ }
2271
+ });
2272
+ return template;
1884
2273
  });
1885
2274
  };
2275
+ const ALLOWED_CREATE_FIELDS = [
2276
+ 'name',
2277
+ 'is_personal',
2278
+ 'is_public',
2279
+ 'sender',
2280
+ 'description',
2281
+ 'roles',
2282
+ 'fields',
2283
+ ];
1886
2284
  /**
1887
- * Get an in-person signing link. Must be called by the owner/creator of the envelope. The response
1888
- * also includes the raw access key that may be used to directly initiate a signing session (see
1889
- * `startSigningSession`) as well as an access token representing a valid signing session for
1890
- * immediate use in embeds or other applications. Note that in-person signing is considered a
1891
- * lower-security operation than authenticated signing, and the final envelope certificate will
1892
- * reflect this.
2285
+ * Create a template.
1893
2286
  *
1894
- * @group Recipients
1895
- * @api POST /v2/sign/in-person/:envelope_id/:role_name Get In-Person Signing Link
1896
- * @apiParam string(format:uuid) envelope_id The envelope to operate on.
1897
- * @apiParam string role_name The role to request.
1898
- * @apiSuccess IInPersonLinkResponse . Signing session token and envelope/recipient metadata.
2287
+ * ```typescript
2288
+ * import {createTemplate} from '@verdocs/js-sdk/Templates';
2289
+ *
2290
+ * const newTemplate = await createTemplate((VerdocsEndpoint.getDefault(), {...});
2291
+ * ```
2292
+ *
2293
+ * @group Templates
2294
+ * @api POST /v2/templates Create a template
2295
+ * @apiBody string name Template name
2296
+ * @apiBody string description? Optional description
2297
+ * @apiBody TTemplateVisibility visibility? Visibility setting
2298
+ * @apiBody boolean is_personal? Deprecated. If true, the template is personal and can only be seen by the caller. (Use "visibility" for new calls.)
2299
+ * @apiBody boolean is_public? Deprecated. If true, the template is public and can be seen by anybody. (Use "visibility" for new calls.)
2300
+ * @apiBody TTemplateSender sender? Who may send envelopes using this template
2301
+ * @apiBody number initial_reminder? Delay (in seconds) before the first reminder is sent (min: 4hrs). Set to 0 or null to disable.
2302
+ * @apiBody number followup_reminders? Delay (in seconds) before the subsequent reminders are sent (min: 12hrs). Set to 0 or null to disable.
2303
+ * @apiBody array(items:object) documents? Optional list of documents to attach to the template
2304
+ * @apiBody array(items:IRole) roles? Optional list of roles to create. Note that if roles are not included in the request, fields will be ignored.
2305
+ * @apiBody array(fields:ITemplateField) fields? Optional list of fields to create. Note that if fields that do not match a role will be ignored.
2306
+ * @apiSuccess ITemplate . The newly-created template
1899
2307
  */
1900
- const getInPersonLink = (endpoint, envelope_id, role_name) => endpoint.api //
1901
- .post(`/v2/sign/in-person/${envelope_id}/${encodeURIComponent(role_name)}`)
1902
- .then((r) => r.data);
2308
+ const createTemplate = (endpoint, params, onUploadProgress) => {
2309
+ const options = {
2310
+ timeout: 120000,
2311
+ onUploadProgress: (event) => {
2312
+ const total = event.total || 1;
2313
+ const loaded = event.loaded || 0;
2314
+ onUploadProgress?.(Math.floor((loaded * 100) / (total)), loaded, total);
2315
+ },
2316
+ };
2317
+ if (params.documents && params.documents[0] instanceof File) {
2318
+ const formData = new FormData();
2319
+ ALLOWED_CREATE_FIELDS.forEach((allowedKey) => {
2320
+ if (params[allowedKey] !== undefined) {
2321
+ formData.append(allowedKey, params[allowedKey]);
2322
+ }
2323
+ });
2324
+ params.documents.forEach((file) => {
2325
+ formData.append('documents', file, file.name);
2326
+ });
2327
+ return endpoint.api.post('/v2/templates', formData, options).then((r) => r.data);
2328
+ }
2329
+ else {
2330
+ return endpoint.api.post('/v2/templates', params, options).then((r) => r.data);
2331
+ }
2332
+ };
1903
2333
  /**
1904
- * Verify a recipient within a signing session. All signing sessions use an invite code at a minimum,
1905
- * but many scenarios require more robust verification of recipients, so one or more verification
1906
- * methods may be attached to each recipient. If an authentication method is enabled, the
1907
- * signer must first accept the e-signature disclosures, then complete each verification step
1908
- * before attempting to view/display documents, complete any fields, or submit the envelope.
1909
- * This endpoint should be called to complete each step. If the call fails an error will be
1910
- * thrown.
2334
+ * Duplicate a template. Creates a complete clone, including all settings (e.g. reminders), fields,
2335
+ * roles, and documents.
1911
2336
  *
1912
- * @group Recipients
1913
- * @api POST /v2/sign/verify Verify recipient/signer
1914
- * @apiParam string(enum:'passcode'|'email'|'sms'|'kba'|'id') auth_method The authentication method being completed
1915
- * @apiParam string code? The passcode or OTP entered. Required for passcode, email, and SMS methods.
1916
- * @apiParam boolean resend? For SMS or email methods, set to send a new code.
1917
- * @apiParam boolean first_name? For KBA, the recipient's first name
1918
- * @apiParam boolean last_name? For KBA, the recipient's last name
1919
- * @apiParam boolean address? For KBA, the recipient's address
1920
- * @apiParam boolean city? For KBA, the recipient's city
1921
- * @apiParam boolean state? For KBA, the recipient's state
1922
- * @apiParam boolean zip? For KBA, the recipient's zip code
1923
- * @apiParam boolean ssn_last_4? For KBA, the last 4 digits of the recipient's SSN
1924
- * @apiParam boolean dob? For KBA, the recipient's date of birth
1925
- * @apiParam array(items:IKBAResponse) responses? For KBA, responses to any challenge questions presented
1926
- * @apiSuccess ISignerTokenResponse . Updated signing session.
2337
+ * ```typescript
2338
+ * import {duplicateTemplate} from '@verdocs/js-sdk/Templates';
2339
+ *
2340
+ * const newTemplate = await duplicateTemplate((VerdocsEndpoint.getDefault(), originalTemplateId, 'My Template Copy');
2341
+ * ```
2342
+ *
2343
+ * @group Templates
2344
+ * @api PUT /v2/templates/:template_id Perform an operation on a template
2345
+ * @apiBody string(enum:'duplicate') action Action to perform
2346
+ * @apiBody string name? If duplicating the template, a name for the new copy
2347
+ * @apiSuccess ITemplate . The newly-copied template
1927
2348
  */
1928
- const verifySigner = (endpoint, params) => endpoint.api //
1929
- .post(`/v2/sign/verify`, params)
2349
+ const duplicateTemplate = (endpoint, templateId, name) => endpoint.api //
2350
+ .put(`/v2/templates/${templateId}`, { action: 'duplicate', name })
1930
2351
  .then((r) => r.data);
1931
2352
  /**
1932
- * Delegate a recipient's signing responsibility. The envelope sender must enable this before the
1933
- * recipient calls this endpoint, and only the recipient may call it, or the call will be rejected.
1934
- * The recipient's role will be renamed and configured to indicate to whom the delegation was made,
1935
- * and a new recipient entry with the updated details (e.g. name and email address) will be added
1936
- * to the flow with the same role_name, order, and sequence of the original recipient. Unless
1937
- * no_contact is set on the envelope, the delegation recipient and envelope creator will also be
1938
- * notified.
2353
+ * Create a template from a Sharepoint asset.
1939
2354
  *
1940
- * @group Recipients
1941
- * @api PUT /v2/envelopes/:envelope_id/recipients/:role_name Delegate Recipient
1942
- * @apiParam string(format:uuid) envelope_id The envelope to operate on.
1943
- * @apiParam string role_name The role to operate on.
1944
- * @apiBody string(enum:'delegate') action The operation to perform (delegate).
1945
- * @apiBody string first_name The first name of the new recipient.
1946
- * @apiBody string last_name The last name of the new recipient.
1947
- * @apiBody string email The email address of the new recipient.
1948
- * @apiBody string phone? Optional phone number for the new recipient.
1949
- * @apiBody string message? Optional phone number for the new recipient's invitation.
1950
- * @apiSuccess string . Success message.
2355
+ * ```typescript
2356
+ * import {createTemplateFromSharepoint} from '@verdocs/js-sdk/Templates';
2357
+ *
2358
+ * const newTemplate = await createTemplateFromSharepoint((VerdocsEndpoint.getDefault(), {...});
2359
+ * ```
2360
+ *
2361
+ * @group Templates
2362
+ * @api POST /v2/templates/from-sharepoint Create a template from an asset in Sharepoint
2363
+ * @apiBody string name Name for the new template
2364
+ * @apiBody string siteId Name for the new template
2365
+ * @apiBody string itemId Name for the new template
2366
+ * @apiBody string oboToken On-Behalf-Of token for calls to Sharepoint. Should be generated as a short-expiration token with at least Read privileges to the siteId/itemId. This token will be discarded after being used.
2367
+ * @apiSuccess ITemplate . The newly-created template
1951
2368
  */
1952
- const delegateRecipient = (endpoint, envelopeId, roleName, params) => endpoint.api //
1953
- .put(`/v2/envelopes/${envelopeId}/recipients/${encodeURIComponent(roleName)}`, { action: 'delegate', ...params })
1954
- .then((r) => r.data);
2369
+ const createTemplateFromSharepoint = (endpoint, params) => {
2370
+ const options = {
2371
+ timeout: 120000,
2372
+ };
2373
+ return endpoint.api.post('/v2/templates/from-sharepoint', params, options).then((r) => r.data);
2374
+ };
1955
2375
  /**
1956
- * Update a recipient. NOTE: User interfaces should rate-limit this operation to avoid spamming recipients.
1957
- * Excessive use of this endpoint may result in Verdocs rate-limiting the calling application to prevent
1958
- * abuse. This endpoint will return a 200 OK even if the no_contact flag is set on the envelope (in which
1959
- * case the call will be silently ignored).
2376
+ * Update a template.
1960
2377
  *
1961
- * @group Recipients
1962
- * @api PATCH /envelopes/:envelope_id/recipients/:role_name Update Recipient
1963
- * @apiParam string(format:uuid) envelope_id The envelope to operate on.
1964
- * @apiParam string role_name The role name to update.
1965
- * @apiBody string(enum:'remind'|'reset') action? Trigger a reminder, or fully reset the recipient
1966
- * @apiBody string first_name? Update the recipient's first name.
1967
- * @apiBody string last_name? Update the recipient's last name.
1968
- * @apiBody string email? Update the recipient's email address.
1969
- * @apiBody string message? Update the recipient's invite message.
1970
- * @apiBody string phone? Update the recipient's phone number.
1971
- * @apiBody string passcode? If passcode authentication is used, the recipient's address to prefill. May only be changed if the recipient has not already completed passcode-based auth.
1972
- * @apiBody string address? If KBA-based authentication is used, the recipient's address to prefill. May only be changed if the recipient has not already completed KBA-based auth.
1973
- * @apiBody string city? If KBA-based authentication is used, the recipient's city to prefill. May only be changed if the recipient has not already completed KBA-based auth.
1974
- * @apiBody string state? If KBA-based authentication is used, the recipient's state to prefill. May only be changed if the recipient has not already completed KBA-based auth.
1975
- * @apiBody string zip? If KBA-based authentication is used, the recipient's zip code to prefill. May only be changed if the recipient has not already completed KBA-based auth.
1976
- * @apiBody string dob? If KBA-based authentication is used, the recipient's date of birth to prefill. May only be changed if the recipient has not already completed KBA-based auth.
1977
- * @apiBody string ssn_last_4? If KBA-based authentication is used, the recipient's SSN-last-4 to prefill. May only be changed if the recipient has not already completed KBA-based auth.
1978
- * @apiSuccess IRecipient . The updated Recipient.
2378
+ * ```typescript
2379
+ * import {updateTemplate} from '@verdocs/js-sdk/Templates';
2380
+ *
2381
+ * const updatedTemplate = await updateTemplate((VerdocsEndpoint.getDefault(), '83da3d70-7857-4392-b876-c4592a304bc9', { name: 'New Name' });
2382
+ * ```
2383
+ *
2384
+ * @group Templates
2385
+ * @api PATCH /v2/templates/:template_id Update a template
2386
+ * @apiBody string name? Template name
2387
+ * @apiBody string description? Optional description
2388
+ * @apiBody TTemplateVisibility visibility? Visibility setting
2389
+ * @apiBody boolean is_personal? Deprecated. If true, the template is personal and can only be seen by the caller. (Use "visibility" for new calls.)
2390
+ * @apiBody boolean is_public? Deprecated. If true, the template is public and can be seen by anybody. (Use "visibility" for new calls.)
2391
+ * @apiBody TTemplateSender sender? Who may send envelopes using this template
2392
+ * @apiBody number initial_reminder? Delay (in seconds) before the first reminder is sent (min: 4hrs). Set to 0 or null to disable.
2393
+ * @apiBody number followup_reminders? Delay (in seconds) before the subsequent reminders are sent (min: 12hrs). Set to 0 or null to disable.
2394
+ * @apiSuccess ITemplate . The updated template
1979
2395
  */
1980
- const updateRecipient = (endpoint, envelopeId, roleName, params) => endpoint.api //
1981
- .patch(`/v2/envelopes/${envelopeId}/recipients/${encodeURIComponent(roleName)}`, params)
2396
+ const updateTemplate = (endpoint, templateId, params) => endpoint.api //
2397
+ .patch(`/v2/templates/${templateId}`, params)
1982
2398
  .then((r) => r.data);
1983
2399
  /**
1984
- * Send a reminder to a recipient. The recipient must still be an active member of the signing flow
1985
- * (e.g. not declined, already submitted, etc.)
2400
+ * Delete a template.
2401
+ *
2402
+ * ```typescript
2403
+ * import {deleteTemplate} from '@verdocs/js-sdk/Templates';
2404
+ *
2405
+ * await deleteTemplate((VerdocsEndpoint.getDefault(), '83da3d70-7857-4392-b876-c4592a304bc9');
2406
+ * ```
2407
+ *
2408
+ * @group Templates
2409
+ * @api DELETE /v2/templates/:template_id Delete a template
2410
+ * @apiSuccess string . Success
1986
2411
  */
1987
- const remindRecipient = (endpoint, envelopeId, roleName) => endpoint.api //
1988
- .patch(`/v2/envelopes/${envelopeId}/recipients/${encodeURIComponent(roleName)}`, { action: 'remind' })
2412
+ const deleteTemplate = (endpoint, templateId) => endpoint.api //
2413
+ .delete(`/v2/templates/${templateId}`)
1989
2414
  .then((r) => r.data);
1990
2415
  /**
1991
- * Fully reset a recipient. This allows the recipient to restart failed KBA flows, change
1992
- * fields they may have filled in incorrectly while signing, etc. This cannot be used on a
1993
- * canceled or completed envelope, but may be used to restart an envelope marked declined.
2416
+ * Toggle the template star for a template.
2417
+ *
2418
+ * ```typescript
2419
+ * import {toggleTemplateStar} from '@verdocs/js-sdk/Templates';
2420
+ *
2421
+ * await toggleTemplateStar((VerdocsEndpoint.getDefault(), '83da3d70-7857-4392-b876-c4592a304bc9');
2422
+ * ```
2423
+ *
2424
+ * @group Templates
2425
+ * @api POST /v2/templates/:template_id/star Star or unstar a template (toggle state)
2426
+ * @apiSuccess ITemplate . Success
1994
2427
  */
1995
- const resetRecipient = (endpoint, envelopeId, roleName) => endpoint.api //
1996
- .patch(`/v2/envelopes/${envelopeId}/recipients/${encodeURIComponent(roleName)}`, { action: 'reset' })
2428
+ const toggleTemplateStar = (endpoint, templateId) => endpoint.api //
2429
+ .post(`/v2/templates/${templateId}/stars/toggle`)
1997
2430
  .then((r) => r.data);
1998
2431
 
1999
2432
  /**
2000
- * Various helpers to identify available operations for an envelope by a user.
2433
+ * A TemplateDocument represents a PDF or other attachment in a Template.
2001
2434
  *
2002
2435
  * @module
2003
2436
  */
2004
2437
  /**
2005
- * Check to see if the profile ID owns the envelope.
2006
- */
2007
- const isEnvelopeOwner = (profile_id, envelope) => envelope.profile_id === profile_id;
2008
- /**
2009
- * Check to see if the profile ID is a recipient within the envelope.
2010
- */
2011
- const isEnvelopeRecipient = (profile_id, envelope) => (envelope.recipients || []).some((recipient) => recipient.profile_id === profile_id);
2012
- /**
2013
- * Check to see if the profile ID is the envelope's sender or one of the recipients.
2014
- */
2015
- const canAccessEnvelope = (profile_id, envelope) => isEnvelopeOwner(profile_id, envelope) || isEnvelopeRecipient(profile_id, envelope);
2016
- /**
2017
- * Check to see if the user owns the envelope.
2018
- */
2019
- const userIsEnvelopeOwner = (profile, envelope) => envelope.profile_id === profile?.id;
2020
- /**
2021
- * Check to see if the user is a recipient within the envelope.
2022
- */
2023
- const userIsEnvelopeRecipient = (profile, envelope) => (envelope.recipients || []).some((recipient) => recipient.profile_id === profile?.id);
2024
- /**
2025
- * Check to see if the profile ID is the envelope's sender or one of the recipients.
2438
+ * Create a Document for a particular Template.
2439
+ *
2440
+ * ```typescript
2441
+ * import {TemplateDocument} from '@verdocs/js-sdk/Templates';
2442
+ *
2443
+ * await TemplateDocument.createDocument((VerdocsEndpoint.getDefault(), templateID, params);
2444
+ * ```
2445
+ *
2446
+ * @group Template Documents
2447
+ * @api POST /v2/templates/:template_id/documents Attach a document to a template
2448
+ * @apiBody string(format:binary) file Document file to attach. The file name will automatically be used as the document name.
2449
+ * @apiBody string(format:uuid) template_id Template ID to attach the document to
2450
+ * @apiSuccess ITemplateDocument . Template document
2026
2451
  */
2027
- const useCanAccessEnvelope = (profile, envelope) => userIsEnvelopeOwner(profile, envelope) || userIsEnvelopeRecipient(profile, envelope);
2452
+ const createTemplateDocument = (endpoint, template_id, file, onUploadProgress) => {
2453
+ const formData = new FormData();
2454
+ formData.append('file', file, file.name);
2455
+ formData.append('template_id', template_id);
2456
+ return endpoint.api //
2457
+ .post(`/v2/template-documents`, formData, {
2458
+ timeout: 120000,
2459
+ onUploadProgress: (event) => {
2460
+ const total = event.total || 1;
2461
+ const loaded = event.loaded || 0;
2462
+ onUploadProgress?.(Math.floor((loaded * 100) / (total)), loaded, total);
2463
+ },
2464
+ })
2465
+ .then((r) => r.data);
2466
+ };
2028
2467
  /**
2029
- * Check to see if the envelope has pending actions.
2468
+ * Delete a specific Document.
2469
+ *
2470
+ * ```typescript
2471
+ * import {TemplateDocument} from '@verdocs/js-sdk/Templates';
2472
+ *
2473
+ * await TemplateDocument.deleteDocument((VerdocsEndpoint.getDefault(), templateID, documentID);
2474
+ * ```
2475
+ *
2476
+ * @group Template Documents
2477
+ * @api DELETE /v2/templates/:temlate_id/documents/:document_id Delete a template document
2478
+ * @apiSuccess string . Success
2030
2479
  */
2031
- const envelopeIsActive = (envelope) => envelope.status !== 'complete' && envelope.status !== 'declined' && envelope.status !== 'canceled';
2480
+ const deleteTemplateDocument = (endpoint, templateId, documentId) => endpoint.api //
2481
+ .delete(`/v2/templates/${templateId}/documents/${documentId}`)
2482
+ .then((r) => r.data);
2032
2483
  /**
2033
- * Check to see if the envelope has been completed.
2484
+ * Get all metadata for a template document. Note that when called by non-creators (e.g. Org Collaborators)
2485
+ * this will return only the **metadata** the caller is allowed to view.
2486
+ *
2487
+ * @group Template Documents
2488
+ * @api GET /v2/envelope-documents/:id Get envelope document
2489
+ * @apiParam string(format: 'uuid') document_id The ID of the document to retrieve.
2490
+ * @apiSuccess IEnvelopeDocument . The detailed metadata for the document requested
2034
2491
  */
2035
- const envelopeIsComplete = (envelope) => envelope.status !== 'complete';
2492
+ const getTemplateDocument = async (endpoint, documentId) => endpoint.api //
2493
+ .get(`/v2/template-documents/${documentId}`)
2494
+ .then((r) => r.data);
2036
2495
  /**
2037
- * Check to see if the user owns the envelope.
2496
+ * Download a document directly.
2038
2497
  */
2039
- const userCanCancelEnvelope = (profile, envelope) => userIsEnvelopeOwner(profile, envelope) &&
2040
- envelope.status !== 'complete' &&
2041
- envelope.status !== 'declined' &&
2042
- envelope.status !== 'canceled';
2498
+ const downloadTemplateDocument = async (endpoint, documentId) => endpoint.api //
2499
+ .get(`/v2/template-documents/${documentId}?type=file`, { responseType: 'blob' })
2500
+ .then((r) => r.data);
2043
2501
  /**
2044
- * Check to see if the user owns the envelope.
2502
+ * Get an envelope document's metadata, or the document itself. If no "type" parameter is specified,
2503
+ * the document metadata is returned. If "type" is set to "file", the document binary content is
2504
+ * returned with Content-Type set to the MIME type of the file. If "type" is set to "download", a
2505
+ * string download link will be returned. If "type" is set to "preview" a string preview link will
2506
+ * be returned. This link expires quickly, so it should be accessed immediately and never shared.
2507
+ *
2508
+ * @group Template Documents
2509
+ * @api GET /v2/envelope-documents/:document_id Preview, Download, or Link to a Document
2510
+ * @apiParam string(format: 'uuid') document_id The ID of the document to retrieve.
2511
+ * @apiQuery string(enum:'file'|'download'|'preview') type? Download the file directly, generate a download link, or generate a preview link.
2512
+ * @apiSuccess string . The generated link.
2045
2513
  */
2046
- const userCanFinishEnvelope = (profile, envelope) => userIsEnvelopeOwner(profile, envelope) &&
2047
- envelope.status !== 'complete' &&
2048
- envelope.status !== 'declined' &&
2049
- envelope.status !== 'canceled';
2514
+ const getTemplateDocumentDownloadLink = async (endpoint, _envelopeId, documentId) => endpoint.api //
2515
+ .get(`/v2/template-documents/${documentId}?type=download`)
2516
+ .then((r) => r.data);
2050
2517
  /**
2051
- * Returns true if the recipient has a pending action. Note that this does not necessarily mean the recipient can act (yet).
2518
+ * Get a pre-signed preview link for an Envelope Document. This link expires quickly, so it should
2519
+ * be accessed immediately and never shared. Content-Disposition will be set to "inline".
2052
2520
  */
2053
- const recipientHasAction = (recipient) => !['submitted', 'canceled', 'declined'].includes(recipient.status);
2521
+ const getTemplateDocumentPreviewLink = async (endpoint, _envelopeId, documentId) => endpoint.api //
2522
+ .get(`/v2/envelope-documents/${documentId}?type=preview`)
2523
+ .then((r) => r.data);
2054
2524
  /**
2055
- * Returns the recipients who still have a pending action. Note that not all of these recipients may be able to act (yet).
2525
+ * Get (binary download) a file attached to a Template. It is important to use this method
2526
+ * rather than a direct A HREF or similar link to set the authorization headers for the
2527
+ * request.
2056
2528
  */
2057
- const getRecipientsWithActions = (envelope) => ['complete', 'declined', 'canceled'].includes(envelope.status) ? [] : (envelope?.recipients || []).filter(recipientHasAction);
2529
+ const getTemplateDocumentFile = async (endpoint, templateId, documentId) => endpoint.api //
2530
+ .get(`/v2/templates/${templateId}/documents/${documentId}?file=true`, { responseType: 'blob' })
2531
+ .then((r) => r.data);
2058
2532
  /**
2059
- * Returns true if the recipient can act.
2533
+ * Get (binary download) a file attached to a Template. It is important to use this method
2534
+ * rather than a direct A HREF or similar link to set the authorization headers for the
2535
+ * request.
2060
2536
  */
2061
- const recipientCanAct = (recipient, recipientsWithActions) => recipient.sequence === recipientsWithActions?.[0]?.sequence;
2537
+ const getTemplateDocumentThumbnail = async (endpoint, templateId, documentId) => endpoint.api //
2538
+ .get(`/v2/templates/${templateId}/documents/${documentId}?thumbnail=true`, { responseType: 'blob' })
2539
+ .then((r) => r.data);
2062
2540
  /**
2063
- * Returns true if the user can act.
2541
+ * Get a display URI for a given page in a file attached to a template document. These pages are rendered server-side
2542
+ * into PNG resources suitable for display in IMG tags although they may be used elsewhere. Note that these are intended
2543
+ * for DISPLAY ONLY, are not legally binding documents, and do not contain any encoded metadata from participants. The
2544
+ * original asset may be obtained by calling `getTemplateDocumentFile()` or similar.
2064
2545
  */
2065
- const userCanAct = (email, recipientsWithActions) => {
2066
- const recipient = recipientsWithActions.find((r) => r.email === email);
2067
- return recipient && recipient.sequence === recipientsWithActions?.[0]?.sequence;
2546
+ const getTemplateDocumentPageDisplayUri = async (endpoint, documentId, page, variant = 'original') => endpoint.api.get(`/v2/template-documents/page-image/${documentId}/${variant}/${page}`, { timeout: 20000 }).then((r) => r.data);
2547
+
2548
+ const EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
2549
+ // @see https://www.regextester.com/1978
2550
+ const PHONE_REGEX = /((?:\+|00)[17](?: |\-)?|(?:\+|00)[1-9]\d{0,2}(?: |\-)?|(?:\+|00)1\-\d{3}(?: |\-)?)?(0\d|\([0-9]{3}\)|[1-9]{0,3})(?:((?: |\-)[0-9]{2}){4}|((?:[0-9]{2}){4})|((?: |\-)[0-9]{3}(?: |\-)[0-9]{4})|([0-9]{7}))/;
2551
+ const URL_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
2552
+ const POSTAL_CODE_REGEX = /^[A-Za-z0-9-\s]{3,10}$/;
2553
+ const NUMBER_REGEX = /^\d+$/;
2554
+ const DATE_REGEX = /^(\d{4}[-\/]\d{2}[-\/]\d{2})|(\d{2}[-\/]\d{2}[-\/]\d{4})$/;
2555
+ const VALIDATORS = {
2556
+ email: { regex: EMAIL_REGEX, label: 'Email Address' },
2557
+ phone: { regex: PHONE_REGEX, label: 'Phone Number' },
2558
+ url: { regex: URL_REGEX, label: 'URL' },
2559
+ postal_code: { regex: POSTAL_CODE_REGEX, label: 'Zip/Postal Code' },
2560
+ number: { regex: NUMBER_REGEX, label: 'Number' },
2561
+ date: { regex: DATE_REGEX, label: 'Date' },
2068
2562
  };
2563
+ const isValidInput = (value, validator) => Object.keys(VALIDATORS).includes(validator) && VALIDATORS[validator].regex.test(value);
2069
2564
  /**
2070
- * Returns true if the user can act.
2565
+ * Get a list of available validators for field inputs. Note that validators always check strings,
2566
+ * because that is all a user can enter in an HTML input field. Numeric-format validators should
2567
+ * perform any necessary conversions internally. Validators never throw - they just return a boolean.
2568
+ * indicating whether the value is valid.
2071
2569
  */
2072
- const userCanSignNow = (profile, envelope) => {
2073
- if (!profile) {
2074
- return false;
2570
+ const getValidators = () => Object.keys(VALIDATORS);
2571
+ const isValidEmail = (email) => !!email && EMAIL_REGEX.test(email);
2572
+ const isValidPhone = (phone) => !!phone && PHONE_REGEX.test(phone);
2573
+ const isValidRoleName = (value, roles) => roles.findIndex((role) => role.name === value) !== -1;
2574
+ const TagRegEx = /^[a-zA-Z0-9-]{0,32}$/;
2575
+ const isValidTag = (value, tags) => TagRegEx.test(value) || tags.findIndex((tag) => tag === value) !== -1;
2576
+
2577
+ const isFieldFilled = (field, allRecipientFields) => {
2578
+ const { value = '' } = field;
2579
+ switch (field.type) {
2580
+ case 'textarea':
2581
+ case 'textbox':
2582
+ switch (field.validator || '') {
2583
+ case 'email':
2584
+ return value && isValidInput(value, 'email');
2585
+ case 'phone':
2586
+ return value && isValidInput(value, 'phone');
2587
+ default:
2588
+ return (value || '').trim() !== '';
2589
+ }
2590
+ case 'signature':
2591
+ return value === 'signed';
2592
+ case 'initial':
2593
+ return value === 'initialed';
2594
+ // Timestamp fields get automatically filled when the envelope is submitted.
2595
+ case 'timestamp':
2596
+ return true;
2597
+ case 'date':
2598
+ return !!value;
2599
+ case 'attachment':
2600
+ return value === 'attached';
2601
+ case 'dropdown':
2602
+ return value !== '';
2603
+ case 'checkbox':
2604
+ return value === 'true';
2605
+ case 'radio':
2606
+ if (!!field.group) {
2607
+ return allRecipientFields.filter((f) => f.group === field.group).some((field) => field.value === 'true');
2608
+ }
2609
+ return field.value === 'true';
2610
+ default:
2611
+ return false;
2075
2612
  }
2076
- const recipientsWithActions = getRecipientsWithActions(envelope);
2077
- const myRecipient = recipientsWithActions.find((r) => r.profile_id === profile?.id || r.email === profile?.email);
2078
- return (myRecipient &&
2079
- envelopeIsActive(envelope) &&
2080
- userIsEnvelopeRecipient(profile, envelope) &&
2081
- recipientCanAct(myRecipient, recipientsWithActions));
2082
2613
  };
2083
- const getNextRecipient = (envelope) => {
2084
- const recipientsWithActions = getRecipientsWithActions(envelope);
2085
- return recipientsWithActions?.[0];
2614
+ // TODO: Only allow !required to bypass validation if the field is empty.
2615
+ const isFieldValid = (field, allRecipientFields) => {
2616
+ return !field.required || isFieldFilled(field, allRecipientFields);
2086
2617
  };
2087
2618
 
2088
2619
  /**
2089
- * Create a signature block. In a typical signing workflow, the user is asked at the beginning of the process to
2090
- * "adopt" a signature block to be used for all signature fields in the document. Thus, this is typically called one
2091
- * time to create and store a signature block. Thereafter, the ID of the signature block may be re-used for each
2092
- * signature field to be "stamped" by the user.
2620
+ * Create an initials block. In a typical signing workflow, the user is asked at the beginning of the process to
2621
+ * "adopt" an initials block to be used for all initials fields in the document. Thus, this is typically called
2622
+ * one time to create and store an initials block. Thereafter, the ID of the initials block may be re-used for each
2623
+ * initials field to be "stamped" by the user.
2093
2624
  *
2094
2625
  * Note: Both "guest" signers and authenticated users can create initials blocks. Guest signers
2095
2626
  * typically only ever have one, tied to that session. But authenticated users can create more than
2096
2627
  * one, and can use them interchangeably.
2097
2628
  *
2098
2629
  * @group Signatures and Initials
2099
- * @api POST /v2/profiles/signatures Create Signature Block
2100
- * @apiBody string signature Blob containing signature image to store.
2101
- * @apiSuccess ISignature . The newly-created signature block.
2630
+ * @api POST /v2/profiles/initials Create Initial Block
2631
+ * @apiBody string initial Blob containing initials image to store.
2632
+ * @apiSuccess IInitial . The newly-created initial block.
2102
2633
  */
2103
- const createSignature = (endpoint, name, signature) => {
2634
+ const createInitials = (endpoint, name, initials) => {
2104
2635
  const data = new FormData();
2105
- data.append('signature', signature, name);
2636
+ data.append('initial', initials, name);
2106
2637
  return endpoint.api //
2107
- .post(`/v2/profiles/signatures`, data)
2638
+ .post(`/v2/profiles/initials`, data)
2108
2639
  .then((r) => r.data);
2109
2640
  };
2110
2641
 
2111
2642
  /**
2112
- * API keys are used to authenticate server-to-server calls. (API keys should **never** be used for client-to-server operations!)
2113
- * To generate a key, either use the Verdocs admin interface and make note of the client_id and client_secret generated, or call
2114
- * createKey as shown below. Then call {@link Users.Auth.authenticateApp} to obtain an access token using the provided ID and
2115
- * secret. Note that server-to-server authentication requests return shorter-lived tokens, so it is important to check the `exp`
2116
- * field and re-authenticate as needed for subsequent calls.
2117
- *
2118
- * API keys may be updated or rotated at any time. Regular rotation is recommended. Rotation will not expire or invalidate
2119
- * existing server-to-server sessions, so it may be done at any time without disrupting your application.
2120
- *
2121
- * @module
2122
- */
2123
- /**
2124
- * Get a list of keys for a given organization. The caller must have admin access to the organization.
2125
- *
2126
- * ```typescript
2127
- * import {getApiKeys} from '@verdocs/js-sdk';
2128
- *
2129
- * const keys = await getApiKeys(ORGID);
2130
- * ```
2131
- *
2132
- * @group API Keys
2133
- * @api GET /v2/api-keys Get API keys
2134
- * @apiSuccess array(items: IApiKey) . A list of the API keys for the caller's organization. Secrets will not be included.
2135
- */
2136
- const getApiKeys = (endpoint) => endpoint.api //
2137
- .get(`/v2/api-keys`)
2138
- .then((r) => r.data);
2139
- /**
2140
- * Create an API key.
2141
- *
2142
- * ```typescript
2143
- * import {createApiKey} from '@verdocs/js-sdk';
2144
- *
2145
- * await createApiKey(ORGID, {name: NEWNAME});
2146
- * ```
2147
- *
2148
- * @group API Keys
2149
- * @api POST /v2/api-keys Create API key
2150
- * @apiBody string name A name used to identify the key in the Verdocs Web App
2151
- * @apiBody string(format:uuid) profile_id The profile ID that calls made using the key will act as
2152
- * @apiBody array(items:string) permission An array of permissions to assign to the new key. Extends (but does not override) the API key's profile permissions.
2153
- * @apiSuccess IApiKey . The newly-created API key, including its secret.
2154
- */
2155
- const createApiKey = (endpoint, params) => endpoint.api //
2156
- .post('/v2/api-keys', params)
2643
+ * Get the current KBA status. Note that this may only be called by the recipient and requires a
2644
+ * valid signing session to proceed. Although the Recipient object itself contains indications of
2645
+ * whether KBA is required, it will not contain the current status of the process. If
2646
+ * `recipient.auth_methods` is set (not empty), and `recipient.kba_completed` is false, this endpoint
2647
+ * should be called to determine the next KBA step required.
2648
+ */
2649
+ const getKbaStep = (endpoint, envelope_id, role_name) => endpoint.api //
2650
+ .get(`/v2/kba/${envelope_id}/${encodeURIComponent(role_name)}`)
2157
2651
  .then((r) => r.data);
2158
2652
  /**
2159
- * Rotate the secret for an API key. The caller must have admin access to the organization.
2160
- *
2161
- * ```typescript
2162
- * import {rotateApiKey} from '@verdocs/js-sdk';
2163
- *
2164
- * const {client_secret: newSecret} = await rotateApiKey(ORGID, CLIENTID);
2165
- * ```
2166
- *
2167
- * @group API Keys
2168
- * @api POST /v2/api-keys/:client_id/rotate Rotate API key
2169
- * @apiParam string(format:uuid) client_id The client ID of the key to rotate
2170
- * @apiSuccess IApiKey . The updated API key with its new secret.
2653
+ * Submit a response to a KBA PIN challenge.
2171
2654
  */
2172
- const rotateApiKey = (endpoint, clientId) => endpoint.api //
2173
- .post(`/v2/api-keys/${clientId}/rotate`)
2655
+ const submitKbaPin = (endpoint, envelope_id, role_name, pin) => endpoint.api //
2656
+ .post(`/v2/kba/pin`, { envelope_id, role_name, pin })
2174
2657
  .then((r) => r.data);
2175
2658
  /**
2176
- * Update an API key to change its assigned Profile ID or Name.
2177
- *
2178
- * ```typescript
2179
- * import {updateApiKey} from '@verdocs/js-sdk';
2180
- *
2181
- * await updateApiKey(ORGID, CLIENTID, {name: NEWNAME});
2182
- * ```
2183
- *
2184
- * @group API Keys
2185
- * @api PATCH /v2/api-keys/:client_id Update API key
2186
- * @apiBody string name? New name for the API key
2187
- * @apiBody array(items:string) permission New array of permissions to assign to the new key. Extends (but does not override) the API key's profile permissions.
2188
- * @apiSuccess IApiKey . The updated API key. The secret will not be included.
2659
+ * Submit an identity response to a KBA challenge.
2189
2660
  */
2190
- const updateApiKey = (endpoint, clientId, params) => endpoint.api //
2191
- .patch(`/v2/api-keys/${clientId}`, params)
2661
+ const submitKbaIdentity = (endpoint, envelope_id, role_name, identity) => endpoint.api //
2662
+ .post(`/v2/kba/identity`, { envelope_id, role_name, identity })
2192
2663
  .then((r) => r.data);
2193
2664
  /**
2194
- * Delete an API key.
2195
- *
2196
- * ```typescript
2197
- * import {deleteApiKey} from '@verdocs/js-sdk';
2198
- *
2199
- * await deleteApiKey(ORGID, CLIENTID);
2200
- * ```
2201
- *
2202
- * @group API Keys
2203
- * @api DELETE /v2/api-keys/:client_id Delete API key
2204
- * @apiSuccess string . Success.
2665
+ * Submit an identity response to a KBA challenge. Answers should be submitted in the same order as
2666
+ * the challenges were listed in `IRecipientKbaStepChallenge.questions`.
2205
2667
  */
2206
- const deleteApiKey = (endpoint, clientId) => endpoint.api //
2207
- .delete(`/v2/api-keys/${clientId}`)
2668
+ const submitKbaChallengeResponse = (endpoint, envelope_id, role_name, responses) => endpoint.api //
2669
+ .post(`/v2/kba/response`, { envelope_id, role_name, responses })
2208
2670
  .then((r) => r.data);
2209
2671
 
2210
2672
  /**
2211
- * An Organization Contact (aka Profile) is an individual user with no access to an organization. These entries
2212
- * appear only in contact lists, usually to populate quick-search dropdowns when sending envelopes.
2673
+ * Agree to electronic signing dislosures.
2213
2674
  *
2214
- * @module
2675
+ * @group Recipients
2676
+ * @api POST /envelopes/:envelope_id/recipients/:role_name/agree Agree to e-Signing Disclosures
2677
+ * @apiParam string(format:uuid) envelope_id The envelope to operate on.
2678
+ * @apiParam string role_name The role to operate on.
2679
+ * @apiSuccess IRecipient . The updated Recipient.
2215
2680
  */
2681
+ const envelopeRecipientAgree = (endpoint, envelopeId, roleName, disclosures) => endpoint.api //
2682
+ .post(`/v2/envelopes/${envelopeId}/recipients/${encodeURIComponent(roleName)}/agree`, { disclosures })
2683
+ .then((r) => r.data);
2216
2684
  /**
2217
- * Get a list of the contacts in the caller's organization.
2218
- *
2219
- * ```typescript
2220
- * import {getOrganizationContacts} from '@verdocs/js-sdk';
2221
- *
2222
- * const members = await getOrganizationContacts(VerdocsEndpoint.getDefault()});
2223
- * ```
2685
+ * Decline electronic signing dislosures. Note that if any recipient declines, the entire envelope
2686
+ * becomes non-viable and later recipients may no longer act. The creator will receive a notification
2687
+ * when this occurs.
2224
2688
  *
2225
- * @group Organization Contacts
2226
- * @api GET /v2/organization-contacts Get a list of organization contacts
2227
- * @apiBody string email Email address for the invitee
2228
- * @apiBody string token Invite token for the invitee
2229
- * @apiSuccess string . Success. The invitation will be marked declined and the token will be invalidated.
2689
+ * @group Recipients
2690
+ * @api POST /envelopes/:envelope_id/recipients/:role_name/decline Decline e-Signing Disclosures
2691
+ * @apiParam string(format:uuid) envelope_id The envelope to operate on.
2692
+ * @apiParam string role_name The role to adjust.
2693
+ * @apiSuccess IRecipient . The updated Recipient.
2230
2694
  */
2231
- const getOrganizationContacts = (endpoint) => endpoint.api //
2232
- .get(`/v2/organization-contacts`)
2695
+ const envelopeRecipientDecline = (endpoint, envelopeId, roleName) => endpoint.api //
2696
+ .post(`/v2/envelopes/${envelopeId}/recipients/${encodeURIComponent(roleName)}/decline`)
2233
2697
  .then((r) => r.data);
2234
2698
  /**
2235
- * Delete a contact from the caller's organization. Note that the caller must be an admin or owner.
2236
- *
2237
- * ```typescript
2238
- * import {deleteOrganizationContact} from '@verdocs/js-sdk';
2239
- *
2240
- * await deleteOrganizationContact(VerdocsEndpoint.getDefault(), 'PROFILEID'});
2241
- * ```
2699
+ * Submit an envelope (signing is finished). Note that all fields must be valid/completed for this to succeed.
2242
2700
  *
2243
- * @group Organization Contacts
2244
- * @api POST /v2/organization-invitations/decline GET a list of pending invitations
2245
- * @apiBody string email Email address for the invitee
2246
- * @apiBody string token Invite token for the invitee
2247
- * @apiSuccess string . Success. The invitation will be marked declined and the token will be invalidated.
2701
+ * @group Recipients
2702
+ * @api POST /envelopes/:envelope_id/recipients/:role_name/submit Submit envelope
2703
+ * @apiParam string(format:uuid) envelope_id The envelope to operate on.
2704
+ * @apiParam string role_name The role to submit.
2705
+ * @apiSuccess IRecipient . The updated Recipient.
2248
2706
  */
2249
- const deleteOrganizationContact = (endpoint, profileId) => endpoint.api //
2250
- .delete(`/v2/organization-contacts/${profileId}`)
2707
+ const envelopeRecipientSubmit = (endpoint, envelopeId, roleName) => endpoint.api //
2708
+ .put(`/v2/envelopes/${envelopeId}/recipients/${roleName}/submit`)
2251
2709
  .then((r) => r.data);
2252
2710
  /**
2253
- * Update a member.
2711
+ * Begin a signing session for an Envelope. This path requires an invite code, and should generally
2712
+ * be called with a NON-default Endpoint to avoid conflicting with any active user session the user
2713
+ * may have. To initiate in-person signing by an authenticated user (e.g. self-signing), call
2714
+ * getInPersonLink() instead. The response from that call includes both a link for direct signing
2715
+ * via a Web browser as well as an in-person access_key. That access_key.key may be used here as well.
2254
2716
  *
2255
- * ```typescript
2256
- * import {createOrganizationContact} from '@verdocs/js-sdk';
2717
+ * @group Recipients
2718
+ * @api POST /v2/sign/unauth/:envelope_id/:role_name/:key Start Signing Session
2719
+ * @apiParam string(format:uuid) envelope_id The envelope to operate on.
2720
+ * @apiParam string role_name The role to request.
2721
+ * @apiParam string key Access key generated by the envelope creator or email/SMS invite.
2722
+ * @apiSuccess ISignerTokenResponse . Signing session token and envelope/recipient metadata.
2723
+ */
2724
+ const startSigningSession = async (endpoint, envelope_id, role_name, key) => {
2725
+ return endpoint.api //
2726
+ .post(`/v2/sign/unauth/${envelope_id}/${encodeURIComponent(role_name)}/${key}`)
2727
+ .then((r) => {
2728
+ endpoint.setToken(r.data.access_token, 'signing');
2729
+ return r.data;
2730
+ });
2731
+ };
2732
+ /**
2733
+ * Get an in-person signing link. Must be called by the owner/creator of the envelope. The response
2734
+ * also includes the raw access key that may be used to directly initiate a signing session (see
2735
+ * `startSigningSession`) as well as an access token representing a valid signing session for
2736
+ * immediate use in embeds or other applications. Note that in-person signing is considered a
2737
+ * lower-security operation than authenticated signing, and the final envelope certificate will
2738
+ * reflect this.
2257
2739
  *
2258
- * const result = await createOrganizationContact(VerdocsEndpoint.getDefault(), 'PROFILEID', {first_name:'First', last_name:'Last', email:'a@b.com'});
2259
- * ```
2740
+ * @group Recipients
2741
+ * @api POST /v2/sign/in-person/:envelope_id/:role_name Get In-Person Signing Link
2742
+ * @apiParam string(format:uuid) envelope_id The envelope to operate on.
2743
+ * @apiParam string role_name The role to request.
2744
+ * @apiSuccess IInPersonLinkResponse . Signing session token and envelope/recipient metadata.
2260
2745
  */
2261
- const createOrganizationContact = (endpoint, params) => endpoint.api //
2262
- .post(`/v2/organization-contacts`, params)
2746
+ const getInPersonLink = (endpoint, envelope_id, role_name) => endpoint.api //
2747
+ .post(`/v2/sign/in-person/${envelope_id}/${encodeURIComponent(role_name)}`)
2263
2748
  .then((r) => r.data);
2264
2749
  /**
2265
- * Update a member.
2266
- *
2267
- * ```typescript
2268
- * import {updateOrganizationContact} from '@verdocs/js-sdk';
2750
+ * Verify a recipient within a signing session. All signing sessions use an invite code at a minimum,
2751
+ * but many scenarios require more robust verification of recipients, so one or more verification
2752
+ * methods may be attached to each recipient. If an authentication method is enabled, the
2753
+ * signer must first accept the e-signature disclosures, then complete each verification step
2754
+ * before attempting to view/display documents, complete any fields, or submit the envelope.
2755
+ * This endpoint should be called to complete each step. If the call fails an error will be
2756
+ * thrown.
2269
2757
  *
2270
- * const result = await updateOrganizationContact(VerdocsEndpoint.getDefault(), 'PROFILEID', {first_name:'NewFirst'});
2271
- * ```
2758
+ * @group Recipients
2759
+ * @api POST /v2/sign/verify Verify recipient/signer
2760
+ * @apiParam string(enum:'passcode'|'email'|'sms'|'kba'|'id') auth_method The authentication method being completed
2761
+ * @apiParam string code? The passcode or OTP entered. Required for passcode, email, and SMS methods.
2762
+ * @apiParam boolean resend? For SMS or email methods, set to send a new code.
2763
+ * @apiParam boolean first_name? For KBA, the recipient's first name
2764
+ * @apiParam boolean last_name? For KBA, the recipient's last name
2765
+ * @apiParam boolean address? For KBA, the recipient's address
2766
+ * @apiParam boolean city? For KBA, the recipient's city
2767
+ * @apiParam boolean state? For KBA, the recipient's state
2768
+ * @apiParam boolean zip? For KBA, the recipient's zip code
2769
+ * @apiParam boolean ssn_last_4? For KBA, the last 4 digits of the recipient's SSN
2770
+ * @apiParam boolean dob? For KBA, the recipient's date of birth
2771
+ * @apiParam array(items:IKBAResponse) responses? For KBA, responses to any challenge questions presented
2772
+ * @apiSuccess ISignerTokenResponse . Updated signing session.
2272
2773
  */
2273
- const updateOrganizationContact = (endpoint, profileId, params) => endpoint.api //
2274
- .patch(`/v2/organization-contacts/${profileId}`, params)
2774
+ const verifySigner = (endpoint, params) => endpoint.api //
2775
+ .post(`/v2/sign/verify`, params)
2275
2776
  .then((r) => r.data);
2276
-
2277
2777
  /**
2278
- * Organizations may contain "Groups" of user profiles, called Members. Groups may have permissions assigned that
2279
- * apply to all Members, making it easy to configure role-based access control (RBAC) within an Organization. Note
2280
- * that permissions are **additive**. A user may be a member of more than one group, and may also have permissions
2281
- * assigned directly. In that case, the user will have the combined set of all permissions inherited from all
2282
- * sources.
2778
+ * Delegate a recipient's signing responsibility. The envelope sender must enable this before the
2779
+ * recipient calls this endpoint, and only the recipient may call it, or the call will be rejected.
2780
+ * The recipient's role will be renamed and configured to indicate to whom the delegation was made,
2781
+ * and a new recipient entry with the updated details (e.g. name and email address) will be added
2782
+ * to the flow with the same role_name, order, and sequence of the original recipient. Unless
2783
+ * no_contact is set on the envelope, the delegation recipient and envelope creator will also be
2784
+ * notified.
2283
2785
  *
2284
- * @module
2786
+ * @group Recipients
2787
+ * @api PUT /v2/envelopes/:envelope_id/recipients/:role_name Delegate Recipient
2788
+ * @apiParam string(format:uuid) envelope_id The envelope to operate on.
2789
+ * @apiParam string role_name The role to operate on.
2790
+ * @apiBody string(enum:'delegate') action The operation to perform (delegate).
2791
+ * @apiBody string first_name The first name of the new recipient.
2792
+ * @apiBody string last_name The last name of the new recipient.
2793
+ * @apiBody string email The email address of the new recipient.
2794
+ * @apiBody string phone? Optional phone number for the new recipient.
2795
+ * @apiBody string message? Optional phone number for the new recipient's invitation.
2796
+ * @apiSuccess string . Success message.
2285
2797
  */
2798
+ const delegateRecipient = (endpoint, envelopeId, roleName, params) => endpoint.api //
2799
+ .put(`/v2/envelopes/${envelopeId}/recipients/${encodeURIComponent(roleName)}`, { action: 'delegate', ...params })
2800
+ .then((r) => r.data);
2286
2801
  /**
2287
- * Get a list of groups for the caller's organization. NOTE: Any organization member may request
2288
- * the list of groups, but only Owners and Admins may update them.
2289
- *
2290
- * ```typescript
2291
- * import {getGroups} from '@verdocs/js-sdk';
2802
+ * Update a recipient. NOTE: User interfaces should rate-limit this operation to avoid spamming recipients.
2803
+ * Excessive use of this endpoint may result in Verdocs rate-limiting the calling application to prevent
2804
+ * abuse. This endpoint will return a 200 OK even if the no_contact flag is set on the envelope (in which
2805
+ * case the call will be silently ignored).
2292
2806
  *
2293
- * const groups = await getGroups();
2294
- * ```
2807
+ * @group Recipients
2808
+ * @api PATCH /envelopes/:envelope_id/recipients/:role_name Update Recipient
2809
+ * @apiParam string(format:uuid) envelope_id The envelope to operate on.
2810
+ * @apiParam string role_name The role name to update.
2811
+ * @apiBody string(enum:'remind'|'reset') action? Trigger a reminder, or fully reset the recipient
2812
+ * @apiBody string first_name? Update the recipient's first name.
2813
+ * @apiBody string last_name? Update the recipient's last name.
2814
+ * @apiBody string email? Update the recipient's email address.
2815
+ * @apiBody string message? Update the recipient's invite message.
2816
+ * @apiBody string phone? Update the recipient's phone number.
2817
+ * @apiBody string passcode? If passcode authentication is used, the recipient's address to prefill. May only be changed if the recipient has not already completed passcode-based auth.
2818
+ * @apiBody string address? If KBA-based authentication is used, the recipient's address to prefill. May only be changed if the recipient has not already completed KBA-based auth.
2819
+ * @apiBody string city? If KBA-based authentication is used, the recipient's city to prefill. May only be changed if the recipient has not already completed KBA-based auth.
2820
+ * @apiBody string state? If KBA-based authentication is used, the recipient's state to prefill. May only be changed if the recipient has not already completed KBA-based auth.
2821
+ * @apiBody string zip? If KBA-based authentication is used, the recipient's zip code to prefill. May only be changed if the recipient has not already completed KBA-based auth.
2822
+ * @apiBody string dob? If KBA-based authentication is used, the recipient's date of birth to prefill. May only be changed if the recipient has not already completed KBA-based auth.
2823
+ * @apiBody string ssn_last_4? If KBA-based authentication is used, the recipient's SSN-last-4 to prefill. May only be changed if the recipient has not already completed KBA-based auth.
2824
+ * @apiSuccess IRecipient . The updated Recipient.
2295
2825
  */
2296
- const getGroups = (endpoint) => endpoint.api //
2297
- .get(`/v2/organization-groups`)
2826
+ const updateRecipient = (endpoint, envelopeId, roleName, params) => endpoint.api //
2827
+ .patch(`/v2/envelopes/${envelopeId}/recipients/${encodeURIComponent(roleName)}`, params)
2298
2828
  .then((r) => r.data);
2299
2829
  /**
2300
- * Get the details for a group, including its member profiles and list of permissions.
2301
- *
2302
- * ```typescript
2303
- * import {getGroup} from '@verdocs/js-sdk/v2/organization-groups';
2304
- *
2305
- * const group = await getGroup(GROUPID);
2306
- * ```
2830
+ * Send a reminder to a recipient. The recipient must still be an active member of the signing flow
2831
+ * (e.g. not declined, already submitted, etc.)
2307
2832
  */
2308
- const getGroup = (endpoint, groupId) => endpoint.api //
2309
- .get(`/v2/organization-groups/${groupId}`)
2833
+ const remindRecipient = (endpoint, envelopeId, roleName) => endpoint.api //
2834
+ .patch(`/v2/envelopes/${envelopeId}/recipients/${encodeURIComponent(roleName)}`, { action: 'remind' })
2310
2835
  .then((r) => r.data);
2311
2836
  /**
2312
- * Create a group. Note that "everyone" is a reserved name and may not be created.
2313
- *
2314
- * ```typescript
2315
- * import {createGroup} from '@verdocs/js-sdk';
2316
- *
2317
- * const group = await createGroup(VerdocsEndpoint.getDefault(), {name:'newgroup'});
2318
- * ```
2837
+ * Fully reset a recipient. This allows the recipient to restart failed KBA flows, change
2838
+ * fields they may have filled in incorrectly while signing, etc. This cannot be used on a
2839
+ * canceled or completed envelope, but may be used to restart an envelope marked declined.
2319
2840
  */
2320
- const createGroup = (endpoint, params) => endpoint.api //
2321
- .post('/v2/organization-groups', params)
2841
+ const resetRecipient = (endpoint, envelopeId, roleName) => endpoint.api //
2842
+ .patch(`/v2/envelopes/${envelopeId}/recipients/${encodeURIComponent(roleName)}`, { action: 'reset' })
2322
2843
  .then((r) => r.data);
2844
+
2323
2845
  /**
2324
- * Update a group. Note that "everyone" is a reserved name and may not be changed.
2325
- *
2326
- * ```typescript
2327
- * import {updateGroup} from '@verdocs/js-sdk';
2846
+ * Various helpers to identify available operations for an envelope by a user.
2328
2847
  *
2329
- * const updated = await updateGroup(VerdocsEndpoint.getDefault(), {name:'newname'});
2330
- * ```
2848
+ * @module
2331
2849
  */
2332
- const updateGroup = (endpoint, groupId, params) => endpoint.api //
2333
- .patch(`/v2/organization-groups/${groupId}`, params)
2334
- .then((r) => r.data);
2335
2850
  /**
2336
- * Get an organization by ID. Note that the "everyone" group cannot be deleted.
2337
- *
2338
- * ```typescript
2339
- * import {deleteGroup} from '@verdocs/js-sdk';
2340
- *
2341
- * await deleteGroup(VerdocsEndpoint.getDefault(), 'ORGID');
2342
- * ```
2851
+ * Check to see if the profile ID owns the envelope.
2343
2852
  */
2344
- const deleteGroup = (endpoint, groupId) => endpoint.api //
2345
- .delete(`/v2/organization-groups/${groupId}`)
2346
- .then((r) => r.data);
2853
+ const isEnvelopeOwner = (profile_id, envelope) => envelope.profile_id === profile_id;
2347
2854
  /**
2348
- * Add a member to a group.
2349
- *
2350
- * ```typescript
2351
- * import {addGroupMember} from '@verdocs/js-sdk';
2352
- *
2353
- * await addGroupMember(VerdocsEndpoint.getDefault(), 'GROUPID', 'PROFILEID');
2354
- * ```
2855
+ * Check to see if the profile ID is a recipient within the envelope.
2355
2856
  */
2356
- const addGroupMember = (endpoint, groupId, profile_id) => endpoint.api //
2357
- .post(`/v2/organization-groups/${groupId}/members`, { profile_id })
2358
- .then((r) => r.data);
2857
+ const isEnvelopeRecipient = (profile_id, envelope) => (envelope.recipients || []).some((recipient) => recipient.profile_id === profile_id);
2359
2858
  /**
2360
- * Remove a member from a group.
2361
- *
2362
- * ```typescript
2363
- * import {deleteGroupMember} from '@verdocs/js-sdk';
2364
- *
2365
- * await deleteGroupMember(VerdocsEndpoint.getDefault(), 'GROUPID', 'PROFILEID');
2366
- * ```
2859
+ * Check to see if the profile ID is the envelope's sender or one of the recipients.
2367
2860
  */
2368
- const deleteGroupMember = (endpoint, groupId, profile_id) => endpoint.api //
2369
- .delete(`/v2/organization-groups/${groupId}/members/${profile_id}`)
2370
- .then((r) => r.data);
2371
-
2861
+ const canAccessEnvelope = (profile_id, envelope) => isEnvelopeOwner(profile_id, envelope) || isEnvelopeRecipient(profile_id, envelope);
2372
2862
  /**
2373
- * An invitation represents an opportunity for a Member to join an Organization.
2374
- *
2375
- * @module
2863
+ * Check to see if the user owns the envelope.
2376
2864
  */
2865
+ const userIsEnvelopeOwner = (profile, envelope) => envelope.profile_id === profile?.id;
2377
2866
  /**
2378
- * Get a list of invitations pending for the caller's organization. The caller must be an admin or owner.
2379
- *
2380
- * @group Organization Invitations
2381
- * @api GET /v2/organization-invitations Get a list of pending invitations
2382
- * @apiBody array(items:TRole) roles URL to send Webhook events to. An empty or invalid URL will disable Webhook calls.
2383
- * @apiBody string first_name First name. The user may override this after accepting the invitation.
2384
- * @apiBody string last_name Last name. The user may override this after accepting the invitation.
2385
- * @apiSuccess array(items:IProfile) . List of caller's current organization's members
2867
+ * Check to see if the user is a recipient within the envelope.
2386
2868
  */
2387
- const getOrganizationInvitations = (endpoint) => endpoint.api //
2388
- .get(`/v2/organization-invitations`)
2389
- .then((r) => r.data);
2869
+ const userIsEnvelopeRecipient = (profile, envelope) => (envelope.recipients || []).some((recipient) => recipient.profile_id === profile?.id);
2390
2870
  /**
2391
- * Invite a new user to join the organization.
2392
- *
2393
- * @group Organization Invitations
2394
- * @api POST /v2/organization-invitations Invite a new user to join the organization
2395
- * @apiBody string email Email address to send the invitation to
2396
- * @apiBody string first_name First name. The user may override this after accepting the invitation.
2397
- * @apiBody string last_name Last name. The user may override this after accepting the invitation.
2398
- * @apiBody TRole role Initial role to assign to the user once they accept.
2399
- * @apiSuccess IOrganizationInvitation . The newly-created invitation.
2871
+ * Check to see if the profile ID is the envelope's sender or one of the recipients.
2400
2872
  */
2401
- const createOrganizationInvitation = (endpoint, params) => endpoint.api //
2402
- .post(`/v2/organization-invitations`, params)
2403
- .then((r) => r.data);
2873
+ const useCanAccessEnvelope = (profile, envelope) => userIsEnvelopeOwner(profile, envelope) || userIsEnvelopeRecipient(profile, envelope);
2404
2874
  /**
2405
- * Delete an invitation. Note that no cancellation message will be sent. Invitations are also one-time-use.
2406
- * If the invitee attempts to join after the invitation is deleted, accepted, or decline, they will be
2407
- * shown an error.
2408
- *
2409
- * @group Organization Invitations
2410
- * @api DELETE /v2/organization-invitations/:email Delete a pending invitation
2411
- * @apiSuccess string . Success
2875
+ * Check to see if the envelope has pending actions.
2412
2876
  */
2413
- const deleteOrganizationInvitation = (endpoint, email) => endpoint.api //
2414
- .delete(`/v2/organization-invitations/${email}`)
2415
- .then((r) => r.data);
2877
+ const envelopeIsActive = (envelope) => envelope.status !== 'complete' && envelope.status !== 'declined' && envelope.status !== 'canceled';
2416
2878
  /**
2417
- * Update an invitation. Note that email may not be changed after the invite is sent. To change
2418
- * an invitee's email, delete the incorrect entry and create one with the correct value.
2419
- *
2420
- * @group Organization Invitations
2421
- * @api PATCH /v2/organization-invitations/:email Update a pending invitation
2422
- * @apiBody string first_name First name. The user may override this after accepting the invitation.
2423
- * @apiBody string last_name Last name. The user may override this after accepting the invitation.
2424
- * @apiBody TRole role Initial role to assign to the user once they accept.
2425
- * @apiSuccess IOrganizationInvitation . The updated invitation.
2879
+ * Check to see if the envelope has been completed.
2426
2880
  */
2427
- const updateOrganizationInvitation = (endpoint, email, params) => endpoint.api //
2428
- .patch(`/v2/organization-invitations/${email}`, params)
2429
- .then((r) => r.data);
2881
+ const envelopeIsComplete = (envelope) => envelope.status !== 'complete';
2430
2882
  /**
2431
- * Send a reminder to the invitee to join the organization.
2432
- *
2433
- * @group Organization Invitations
2434
- * @api POST /v2/organization-invitations/resend Send a reminder to a pending invitee
2435
- * @apiBody string email The recipient to send the reminder to
2436
- * @apiSuccess IOrganizationInvitation . The updated invitation
2883
+ * Check to see if the user owns the envelope.
2437
2884
  */
2438
- const resendOrganizationInvitation = (endpoint, email) => endpoint.api //
2439
- .post('/v2/organization-invitations/resend', { email })
2440
- .then((r) => r.data);
2885
+ const userCanCancelEnvelope = (profile, envelope) => userIsEnvelopeOwner(profile, envelope) &&
2886
+ envelope.status !== 'complete' &&
2887
+ envelope.status !== 'declined' &&
2888
+ envelope.status !== 'canceled';
2441
2889
  /**
2442
- * Get an invitation's details. This is generally used as the first step of accepting the invite.
2443
- * A successful response will indicate that the invite token is still valid, and include some
2444
- * metadata for the organization to style the acceptance screen.
2445
- *
2446
- * @group Organization Invitations
2447
- * @api GET /v2/organization-invitations/:email/:token Get a pending invitation (_Authenticated via invite token, not an active session._). Intended to be called by the invitee to get details about the invitation they are about to accept.
2448
- * @apiSuccess IOrganizationInvitation . Requested invitation's details. Will always include summary details for the organization, to be used for branding the accept-invite view.
2890
+ * Check to see if the user owns the envelope.
2449
2891
  */
2450
- const getOrganizationInvitation = (endpoint, email, token) => endpoint.api //
2451
- .get(`/v2/organization-invitations/${email}/${token}`)
2452
- .then((r) => r.data);
2892
+ const userCanFinishEnvelope = (profile, envelope) => userIsEnvelopeOwner(profile, envelope) &&
2893
+ envelope.status !== 'complete' &&
2894
+ envelope.status !== 'declined' &&
2895
+ envelope.status !== 'canceled';
2453
2896
  /**
2454
- * Accept an invitation. This will automatically create a user record for the caller as well as a profile
2455
- * with the appropriate role as specified in the invite. The profile will be set as "current" for the caller,
2456
- * and session tokens will be returned to access the new profile. The profile's email_verified flag will
2457
- * also be set to true.
2458
- *
2459
- * @group Organization Invitations
2460
- * @api POST /v2/organization-invitations/accept Accept an invitation
2461
- * @apiBody string email Email address for the invitee
2462
- * @apiBody string token Invite token for the invitee
2463
- * @apiBody string first_name First name
2464
- * @apiBody string last_name Last name
2465
- * @apiBody string password Password
2466
- * @apiSuccess IAuthenticateResponse . Session credentials for the newly-created user's profile. If the user already had a profile for another organization, the new profile will be made "current" automatically.
2897
+ * Returns true if the recipient has a pending action. Note that this does not necessarily mean the recipient can act (yet).
2467
2898
  */
2468
- const acceptOrganizationInvitation = (endpoint, params) => endpoint.api //
2469
- .post('/v2/organization-invitations/accept', params)
2470
- .then((r) => r.data);
2899
+ const recipientHasAction = (recipient) => !['submitted', 'canceled', 'declined'].includes(recipient.status);
2471
2900
  /**
2472
- * Decline an invitation. This will mark the status "declined," providing a visual indication to the
2473
- * organization's admins that the invite was declined, preventing further invites from being created
2474
- * to the same email address, and also preventing the invitee from receiving reminders to join.
2475
- *
2476
- * @group Organization Invitations
2477
- * @api POST /v2/organization-invitations/decline Decline an invitation
2478
- * @apiDescription Mark the status "declined," providing a visual indication to the organization's admins that the invite was declined, preventing further invites from being created to the same email address, and also preventing the invitee from receiving reminders to join.
2479
- * @apiBody string email Email address for the invitee
2480
- * @apiBody string token Invite token for the invitee
2481
- * @apiSuccess string . Success. The invitation will be marked declined and the token will be invalidated.
2901
+ * Returns the recipients who still have a pending action. Note that not all of these recipients may be able to act (yet).
2482
2902
  */
2483
- const declineOrganizationInvitation = (endpoint, email, token) => endpoint.api //
2484
- .post('/v2/organization-invitations/decline', { email, token })
2485
- .then((r) => r.data);
2486
-
2903
+ const getRecipientsWithActions = (envelope) => ['complete', 'declined', 'canceled'].includes(envelope.status) ? [] : (envelope?.recipients || []).filter(recipientHasAction);
2487
2904
  /**
2488
- * An Organization Member (aka Profile) is an individual user with access to an organization.
2489
- *
2490
- * @module
2905
+ * Returns true if the recipient can act.
2491
2906
  */
2907
+ const recipientCanAct = (recipient, recipientsWithActions) => recipient.sequence === recipientsWithActions?.[0]?.sequence;
2492
2908
  /**
2493
- * Get a list of the members in the caller's organization.
2494
- *
2495
- * ```typescript
2496
- * import {getOrganizationMembers} from '@verdocs/js-sdk';
2497
- *
2498
- * const members = await getOrganizationMembers(VerdocsEndpoint.getDefault()});
2499
- * ```
2500
- *
2501
- * @group Organization Members
2502
- * @api GET /v2/organization-members List current organization's members
2503
- * @apiSuccess array(items:IProfile) . List of caller's current organization's members
2909
+ * Returns true if the user can act.
2504
2910
  */
2505
- const getOrganizationMembers = (endpoint) => endpoint.api //
2506
- .get(`/v2/organization-members`)
2507
- .then((r) => r.data);
2911
+ const userCanAct = (email, recipientsWithActions) => {
2912
+ const recipient = recipientsWithActions.find((r) => r.email === email);
2913
+ return recipient && recipient.sequence === recipientsWithActions?.[0]?.sequence;
2914
+ };
2508
2915
  /**
2509
- * Delete a member from the caller's organization. Note that the caller must be an admin or owner,
2510
- * may not delete him/herself.
2511
- *
2512
- * ```typescript
2513
- * import {deleteOrganizationMember} from '@verdocs/js-sdk';
2514
- *
2515
- * await deleteOrganizationMember(VerdocsEndpoint.getDefault(), 'PROFILEID'});
2516
- * ```
2517
- *
2518
- * @group Organization Members
2519
- * @api DELETE /v2/organization-members/:profile_id Delete a member from the organization
2520
- * @apiSuccess string . Success
2916
+ * Returns true if the user can act.
2521
2917
  */
2522
- const deleteOrganizationMember = (endpoint, profileId) => endpoint.api //
2523
- .delete(`/v2/organization-members/${profileId}`)
2524
- .then((r) => r.data);
2918
+ const userCanSignNow = (profile, envelope) => {
2919
+ if (!profile) {
2920
+ return false;
2921
+ }
2922
+ const recipientsWithActions = getRecipientsWithActions(envelope);
2923
+ const myRecipient = recipientsWithActions.find((r) => r.profile_id === profile?.id || r.email === profile?.email);
2924
+ return (myRecipient &&
2925
+ envelopeIsActive(envelope) &&
2926
+ userIsEnvelopeRecipient(profile, envelope) &&
2927
+ recipientCanAct(myRecipient, recipientsWithActions));
2928
+ };
2929
+ const getNextRecipient = (envelope) => {
2930
+ const recipientsWithActions = getRecipientsWithActions(envelope);
2931
+ return recipientsWithActions?.[0];
2932
+ };
2933
+
2525
2934
  /**
2526
- * Update a member.
2527
- *
2528
- * ```typescript
2529
- * import {updateOrganizationMember} from '@verdocs/js-sdk';
2935
+ * Create a signature block. In a typical signing workflow, the user is asked at the beginning of the process to
2936
+ * "adopt" a signature block to be used for all signature fields in the document. Thus, this is typically called one
2937
+ * time to create and store a signature block. Thereafter, the ID of the signature block may be re-used for each
2938
+ * signature field to be "stamped" by the user.
2530
2939
  *
2531
- * const result = await updateOrganizationMember(VerdocsEndpoint.getDefault(), 'PROFILEID', {roles:['member']});
2532
- * ```
2940
+ * Note: Both "guest" signers and authenticated users can create initials blocks. Guest signers
2941
+ * typically only ever have one, tied to that session. But authenticated users can create more than
2942
+ * one, and can use them interchangeably.
2533
2943
  *
2534
- * @group Organization Members
2535
- * @api PATCH /v2/organization-members/:profile_id Update an organization member.
2536
- * @apiBody array(items:TRole) roles URL to send Webhook events to. An empty or invalid URL will disable Webhook calls.
2537
- * @apiBody string first_name Set to true to enable Webhooks calls.
2538
- * @apiBody string last_name Record<TWebhookEvent, boolean> map of events to enable/disable.
2539
- * @apiSuccess array(items:IProfile) . List of caller's current organization's members
2944
+ * @group Signatures and Initials
2945
+ * @api POST /v2/profiles/signatures Create Signature Block
2946
+ * @apiBody string signature Blob containing signature image to store.
2947
+ * @apiSuccess ISignature . The newly-created signature block.
2540
2948
  */
2541
- const updateOrganizationMember = (endpoint, profileId, params) => endpoint.api //
2542
- .patch(`/v2/organization-members/${profileId}`, params)
2543
- .then((r) => r.data);
2949
+ const createSignature = (endpoint, name, signature) => {
2950
+ const data = new FormData();
2951
+ data.append('signature', signature, name);
2952
+ return endpoint.api //
2953
+ .post(`/v2/profiles/signatures`, data)
2954
+ .then((r) => r.data);
2955
+ };
2544
2956
 
2545
2957
  /**
2546
- * An Organization is the top level object for ownership for Members, Documents, and Templates.
2547
- *
2548
- * NOTE: There is no call specifically to create an organization. Every organization must have
2549
- * at least one "owner" type member. To create a new organization, call createProfile() with
2550
- * the desired new orgName to create. The caller will become the first owner of the new org, and
2551
- * can then invite new members to join as well.
2958
+ * These disclosures will be used if no overrides are supplied by the caller. Overrides must
2959
+ * be applied at the Organization level before creating an envelope.
2960
+ */
2961
+ const DEFAULT_DISCLOSURES = `
2962
+ <ul>
2963
+ <li>
2964
+ Agree to use electronic records and signatures, and confirm you have read the
2965
+ <a href="https://verdocs.com/en/electronic-record-signature-disclosure/" target="_blank">
2966
+ Electronic Record and Signatures Disclosure</a>.</li>
2967
+ <li>
2968
+ Agree to Verdocs'
2969
+ <a href="https://verdocs.com/en/eula" target="_blank">
2970
+ End User License Agreement</a>
2971
+ and confirm you have read Verdocs'
2972
+ <a href="https://verdocs.com/en/privacy-policy/" target="_blank">
2973
+ Privacy Policy</a>.
2974
+ </li>
2975
+ </ul>`;
2976
+
2977
+ /**
2978
+ * API keys are used to authenticate server-to-server calls. (API keys should **never** be used for client-to-server operations!)
2979
+ * To generate a key, either use the Verdocs admin interface and make note of the client_id and client_secret generated, or call
2980
+ * createKey as shown below. Then call {@link Users.Auth.authenticateApp} to obtain an access token using the provided ID and
2981
+ * secret. Note that server-to-server authentication requests return shorter-lived tokens, so it is important to check the `exp`
2982
+ * field and re-authenticate as needed for subsequent calls.
2552
2983
  *
2553
- * NOTE: There is no call to delete an organization. For safety, this is a manual process. Please
2554
- * contact support@verdocs.com if you wish to completely delete an organization and all its records.
2984
+ * API keys may be updated or rotated at any time. Regular rotation is recommended. Rotation will not expire or invalidate
2985
+ * existing server-to-server sessions, so it may be done at any time without disrupting your application.
2555
2986
  *
2556
2987
  * @module
2557
2988
  */
2558
2989
  /**
2559
- * Get an organization by ID. Note that this endpoint will return only a subset of fields
2560
- * if the caller is not a member of the organization (the public fields).
2990
+ * Get a list of keys for a given organization. The caller must have admin access to the organization.
2561
2991
  *
2562
2992
  * ```typescript
2563
- * import {getOrganization} from '@verdocs/js-sdk';
2993
+ * import {getApiKeys} from '@verdocs/js-sdk';
2564
2994
  *
2565
- * const organizations = await getOrganization(VerdocsEndpoint.getDefault(), 'ORGID');
2995
+ * const keys = await getApiKeys(ORGID);
2566
2996
  * ```
2567
2997
  *
2568
- * @group Organizations
2569
- * @api GET /v2/organizations/:organization_id Get organization
2570
- * @apiSuccess IOrganization . The requested organization. The caller must be a member.
2998
+ * @group API Keys
2999
+ * @api GET /v2/api-keys Get API keys
3000
+ * @apiSuccess array(items: IApiKey) . A list of the API keys for the caller's organization. Secrets will not be included.
2571
3001
  */
2572
- const getOrganization = (endpoint, organizationId) => endpoint.api //
2573
- .get(`/v2/organizations/${organizationId}`)
3002
+ const getApiKeys = (endpoint) => endpoint.api //
3003
+ .get(`/v2/api-keys`)
2574
3004
  .then((r) => r.data);
2575
3005
  /**
2576
- * Get an organization's "children".
3006
+ * Create an API key.
2577
3007
  *
2578
3008
  * ```typescript
2579
- * import {getOrganizationChildren} from '@verdocs/js-sdk';
3009
+ * import {createApiKey} from '@verdocs/js-sdk';
2580
3010
  *
2581
- * const children = await getOrganizationChildren(VerdocsEndpoint.getDefault(), 'ORGID');
3011
+ * await createApiKey(ORGID, {name: NEWNAME});
2582
3012
  * ```
2583
3013
  *
2584
- * @group Organizations
2585
- * @api GET /v2/organizations/:organization_id/children Get an organization's children
2586
- * @apiSuccess IOrganization[] . Any child organizations found.
3014
+ * @group API Keys
3015
+ * @api POST /v2/api-keys Create API key
3016
+ * @apiBody string name A name used to identify the key in the Verdocs Web App
3017
+ * @apiBody string(format:uuid) profile_id The profile ID that calls made using the key will act as
3018
+ * @apiBody array(items:string) permission An array of permissions to assign to the new key. Extends (but does not override) the API key's profile permissions.
3019
+ * @apiSuccess IApiKey . The newly-created API key, including its secret.
2587
3020
  */
2588
- const getOrganizationChildren = (endpoint, organizationId) => endpoint.api //
2589
- .get(`/v2/organizations/${organizationId}/children`)
3021
+ const createApiKey = (endpoint, params) => endpoint.api //
3022
+ .post('/v2/api-keys', params)
2590
3023
  .then((r) => r.data);
2591
3024
  /**
2592
- * Get an organization's usage data. If the organization is a parent, usage data for children
2593
- * will be included as well. The response will be a nested object keyed by organization ID,
2594
- * with each entry being a dictionary of usageType:count entries.
3025
+ * Rotate the secret for an API key. The caller must have admin access to the organization.
2595
3026
  *
2596
3027
  * ```typescript
2597
- * import {getOrganizationUsage} from '@verdocs/js-sdk';
3028
+ * import {rotateApiKey} from '@verdocs/js-sdk';
2598
3029
  *
2599
- * const usage = await getOrganizationUsage(VerdocsEndpoint.getDefault(), 'ORGID');
3030
+ * const {client_secret: newSecret} = await rotateApiKey(ORGID, CLIENTID);
2600
3031
  * ```
2601
3032
  *
2602
- * @group Organizations
2603
- * @api GET /v2/organizations/:organization_id/usage Get an organization's usage metrics
2604
- * @apiSuccess TOrganizationUsage . Usage data grouped by organization ID
3033
+ * @group API Keys
3034
+ * @api POST /v2/api-keys/:client_id/rotate Rotate API key
3035
+ * @apiParam string(format:uuid) client_id The client ID of the key to rotate
3036
+ * @apiSuccess IApiKey . The updated API key with its new secret.
2605
3037
  */
2606
- const getOrganizationUsage = (endpoint, organizationId, params) => endpoint.api //
2607
- .get(`/v2/organizations/${organizationId}/usage`, { params })
3038
+ const rotateApiKey = (endpoint, clientId) => endpoint.api //
3039
+ .post(`/v2/api-keys/${clientId}/rotate`)
2608
3040
  .then((r) => r.data);
2609
3041
  /**
2610
- * Create an organization. The caller will be assigned an "Owner" profile in the new organization,
2611
- * and it will be set to "current" automatically. A new set of session tokens will be issued to
2612
- * the caller, and the caller should update their endpoint to use the new tokens.
3042
+ * Update an API key to change its assigned Profile ID or Name.
2613
3043
  *
2614
3044
  * ```typescript
2615
- * import {createOrganization} from '@verdocs/js-sdk';
3045
+ * import {updateApiKey} from '@verdocs/js-sdk';
2616
3046
  *
2617
- * const organization = await createOrganization(VerdocsEndpoint.getDefault(), {name: 'NewOrg'});
3047
+ * await updateApiKey(ORGID, CLIENTID, {name: NEWNAME});
2618
3048
  * ```
2619
3049
  *
2620
- * @group Organizations
2621
- * @api POST /v2/organizations Create organization
2622
- * @apiDescription The caller will be assigned an "Owner" profile in the new organization, and it will be set to "current" automatically. A new set of session tokens will be issued to the caller, and the caller should update their endpoint to use the new tokens.
2623
- * @apiBody string name The name of the new organization
2624
- * @apiBody string parent_id? If set, the new organization will be created as a child of the specified parent organization. The caller must be an admin of the parent organization.
2625
- * @apiBody string contact_email? Contact email for the new organization
2626
- * @apiBody string url? URL for the new organization
2627
- * @apiBody string full_logo_url? URL of a large-format PNG logo
2628
- * @apiBody string thumbnail_url? URL of a small-format (square is recommended) PNG logo
2629
- * @apiBody string primary_color? URL of a small-format (square is recommended) PNG logo
2630
- * @apiBody string secondary_color? URL of a small-format (square is recommended) PNG logo
2631
- * @apiSuccess IAuthenticateResponse . Authentication credentials for user in the new organization. The user will be made an Owner automatically.
3050
+ * @group API Keys
3051
+ * @api PATCH /v2/api-keys/:client_id Update API key
3052
+ * @apiBody string name? New name for the API key
3053
+ * @apiBody array(items:string) permission New array of permissions to assign to the new key. Extends (but does not override) the API key's profile permissions.
3054
+ * @apiSuccess IApiKey . The updated API key. The secret will not be included.
2632
3055
  */
2633
- const createOrganization = (endpoint, params) => endpoint.api //
2634
- .post(`/v2/organizations`, params)
3056
+ const updateApiKey = (endpoint, clientId, params) => endpoint.api //
3057
+ .patch(`/v2/api-keys/${clientId}`, params)
2635
3058
  .then((r) => r.data);
2636
3059
  /**
2637
- * Update an organization. This can only be called by an admin or owner.
3060
+ * Delete an API key.
2638
3061
  *
2639
3062
  * ```typescript
2640
- * import {updateOrganization} from '@verdocs/js-sdk';
3063
+ * import {deleteApiKey} from '@verdocs/js-sdk';
2641
3064
  *
2642
- * const organizations = await updateOrganization(VerdocsEndpoint.getDefault(), organizationId, {name:'ORGNAME'});
3065
+ * await deleteApiKey(ORGID, CLIENTID);
2643
3066
  * ```
2644
3067
  *
2645
- * @group Organizations
2646
- * @api PATCH /v2/organizations/:organization_id Update organization
2647
- * @apiBody string name The name of the new organization
2648
- * @apiBody string contact_email? Contact email for the new organization
2649
- * @apiBody string url? URL for the new organization
2650
- * @apiBody string full_logo_url? URL of a large-format PNG logo
2651
- * @apiBody string thumbnail_url? URL of a small-format (square is recommended) PNG logo
2652
- * @apiBody string primary_color? URL of a small-format (square is recommended) PNG logo
2653
- * @apiBody string secondary_color? URL of a small-format (square is recommended) PNG logo
2654
- * @apiSuccess IOrganization . The details for the updated organization
3068
+ * @group API Keys
3069
+ * @api DELETE /v2/api-keys/:client_id Delete API key
3070
+ * @apiSuccess string . Success.
2655
3071
  */
2656
- const updateOrganization = (endpoint, organizationId, params) => endpoint.api //
2657
- .patch(`/v2/organizations/${organizationId}`, params)
3072
+ const deleteApiKey = (endpoint, clientId) => endpoint.api //
3073
+ .delete(`/v2/api-keys/${clientId}`)
2658
3074
  .then((r) => r.data);
3075
+
2659
3076
  /**
2660
- * Delete an organization. This can only be called by an owner. Inclusion of the organization ID to delete
2661
- * is just a safety check. The caller may only delete the organization they have currently selected.
3077
+ * An Organization Contact (aka Profile) is an individual user with no access to an organization. These entries
3078
+ * appear only in contact lists, usually to populate quick-search dropdowns when sending envelopes.
3079
+ *
3080
+ * @module
3081
+ */
3082
+ /**
3083
+ * Get a list of the contacts in the caller's organization.
2662
3084
  *
2663
3085
  * ```typescript
2664
- * import {deleteOrganization} from '@verdocs/js-sdk';
3086
+ * import {getOrganizationContacts} from '@verdocs/js-sdk';
2665
3087
  *
2666
- * const newSession = await deleteOrganization(VerdocsEndpoint.getDefault(), organizationId);
3088
+ * const members = await getOrganizationContacts(VerdocsEndpoint.getDefault()});
2667
3089
  * ```
2668
3090
  *
2669
- * @group Organizations
2670
- * @api DELETE /v2/organizations/:organization_id Delete organization
2671
- * @apiSuccess IAuthenticateResponse . If the caller is a member of another organization, authentication credentials for the next organization available. If not, this will be null and the caller will be logged out.
3091
+ * @group Organization Contacts
3092
+ * @api GET /v2/organization-contacts Get a list of organization contacts
3093
+ * @apiBody string email Email address for the invitee
3094
+ * @apiBody string token Invite token for the invitee
3095
+ * @apiSuccess string . Success. The invitation will be marked declined and the token will be invalidated.
2672
3096
  */
2673
- const deleteOrganization = (endpoint, organizationId) => endpoint.api //
2674
- .delete(`/v2/organizations/${organizationId}`)
3097
+ const getOrganizationContacts = (endpoint) => endpoint.api //
3098
+ .get(`/v2/organization-contacts`)
2675
3099
  .then((r) => r.data);
2676
3100
  /**
2677
- * Update the organization's full or thumbnail logo. This can only be called by an admin or owner.
3101
+ * Delete a contact from the caller's organization. Note that the caller must be an admin or owner.
2678
3102
  *
2679
3103
  * ```typescript
2680
- * import {updateOrganizationLogo} from '@verdocs/js-sdk';
3104
+ * import {deleteOrganizationContact} from '@verdocs/js-sdk';
2681
3105
  *
2682
- * await updateOrganizationLogo((VerdocsEndpoint.getDefault(), organizationId, file);
3106
+ * await deleteOrganizationContact(VerdocsEndpoint.getDefault(), 'PROFILEID'});
2683
3107
  * ```
2684
3108
  *
2685
- * @group Organizations
2686
- * @api PATCH /v2/organizations/:organization_id Update organization full or thumbnail logo.
2687
- * @apiBody image/png logo? Form-url-encoded file to upload
2688
- * @apiBody image/png thumbnail? Form-url-encoded file to upload
2689
- * @apiSuccess IOrganization . The updated organization.
3109
+ * @group Organization Contacts
3110
+ * @api POST /v2/organization-invitations/decline GET a list of pending invitations
3111
+ * @apiBody string email Email address for the invitee
3112
+ * @apiBody string token Invite token for the invitee
3113
+ * @apiSuccess string . Success. The invitation will be marked declined and the token will be invalidated.
2690
3114
  */
2691
- const updateOrganizationLogo = (endpoint, organizationId, file, onUploadProgress) => {
2692
- const formData = new FormData();
2693
- formData.append('logo', file, file.name);
2694
- return endpoint.api //
2695
- .patch(`/v2/organizations/${organizationId}`, formData, {
2696
- timeout: 120000,
2697
- onUploadProgress: (event) => {
2698
- const total = event.total || 1;
2699
- const loaded = event.loaded || 0;
2700
- onUploadProgress?.(Math.floor((loaded * 100) / (total)), loaded, total);
2701
- },
2702
- })
2703
- .then((r) => r.data);
2704
- };
3115
+ const deleteOrganizationContact = (endpoint, profileId) => endpoint.api //
3116
+ .delete(`/v2/organization-contacts/${profileId}`)
3117
+ .then((r) => r.data);
2705
3118
  /**
2706
- * Update the organization's thumbnail. This can only be called by an admin or owner.
3119
+ * Update a member.
2707
3120
  *
2708
3121
  * ```typescript
2709
- * import {updateOrganizationThumbnail} from '@verdocs/js-sdk';
3122
+ * import {createOrganizationContact} from '@verdocs/js-sdk';
2710
3123
  *
2711
- * await updateOrganizationThumbnail((VerdocsEndpoint.getDefault(), organizationId, file);
3124
+ * const result = await createOrganizationContact(VerdocsEndpoint.getDefault(), 'PROFILEID', {first_name:'First', last_name:'Last', email:'a@b.com'});
2712
3125
  * ```
2713
3126
  */
2714
- const updateOrganizationThumbnail = (endpoint, organizationId, file, onUploadProgress) => {
2715
- const formData = new FormData();
2716
- formData.append('thumbnail', file, file.name);
2717
- return endpoint.api //
2718
- .patch(`/v2/organizations/${organizationId}`, formData, {
2719
- timeout: 120000,
2720
- onUploadProgress: (event) => {
2721
- const total = event.total || 1;
2722
- const loaded = event.loaded || 0;
2723
- onUploadProgress?.(Math.floor((loaded * 100) / (total)), loaded, total);
2724
- },
2725
- })
2726
- .then((r) => r.data);
2727
- };
2728
- const getEntitlements = async (endpoint) => endpoint.api.get(`/v2/organizations/entitlements`).then((r) => r.data);
3127
+ const createOrganizationContact = (endpoint, params) => endpoint.api //
3128
+ .post(`/v2/organization-contacts`, params)
3129
+ .then((r) => r.data);
2729
3130
  /**
2730
- * Largely intended to be used internally by Web SDK components but may be informative for other cases.
2731
- * Entitlements are feature grants such as "ID-based KBA" that require paid contracts to enable, typically
2732
- * because the underlying services that support them are fee-based. Entitlements may run concurrently,
2733
- * and may have different start/end dates e.g. "ID-based KBA" may run 1/1/2026-12/31/2026 while
2734
- * "SMS Authentication" may be added later and run 6/1/2026-5/31/2027. The entitlements list is a simple
2735
- * array of enablements and may include entries that are not YET enabled or have now expired.
2736
- *
2737
- * In client code it is helpful to simply know "is XYZ feature currently enabled?" This function collapses
2738
- * the entitlements list to a simplified dictionary of current/active entitlements. Note that it is async
2739
- * because it calls the server to obtain the "most current" entitlements list. Existence of an entry in the
2740
- * resulting dictionary implies the feature is active. Metadata inside each entry can be used to determine
2741
- * limits, etc.
3131
+ * Update a member.
2742
3132
  *
2743
3133
  * ```typescript
2744
- * import {getActiveEntitlements} from '@verdocs/js-sdk';
2745
- *
2746
- * const activeEntitlements = await getActiveEntitlements((VerdocsEndpoint.getDefault());
2747
- * const isSMSEnabled = !!activeEntitlements.sms_auth;
2748
- * const monthlyKBALimit = activeEntitlements.kba_auth?.monthly_max;
2749
- * ```
2750
- */
2751
- const getActiveEntitlements = async (endpoint) => {
2752
- if (!endpoint.session) {
2753
- throw new Error('No active session');
2754
- }
2755
- const entitlements = await getEntitlements(endpoint);
2756
- return collapseEntitlements(entitlements);
2757
- };
3134
+ * import {updateOrganizationContact} from '@verdocs/js-sdk';
3135
+ *
3136
+ * const result = await updateOrganizationContact(VerdocsEndpoint.getDefault(), 'PROFILEID', {first_name:'NewFirst'});
3137
+ * ```
3138
+ */
3139
+ const updateOrganizationContact = (endpoint, profileId, params) => endpoint.api //
3140
+ .patch(`/v2/organization-contacts/${profileId}`, params)
3141
+ .then((r) => r.data);
2758
3142
 
2759
3143
  /**
2760
- * Webhooks are callback triggers from Verdocs to your servers that notify your applications
2761
- * of various events, such as signing operations.
3144
+ * Organizations may contain "Groups" of user profiles, called Members. Groups may have permissions assigned that
3145
+ * apply to all Members, making it easy to configure role-based access control (RBAC) within an Organization. Note
3146
+ * that permissions are **additive**. A user may be a member of more than one group, and may also have permissions
3147
+ * assigned directly. In that case, the user will have the combined set of all permissions inherited from all
3148
+ * sources.
2762
3149
  *
2763
3150
  * @module
2764
3151
  */
2765
3152
  /**
2766
- * Get the registered Webhook configuration for the caller's organization.
3153
+ * Get a list of groups for the caller's organization. NOTE: Any organization member may request
3154
+ * the list of groups, but only Owners and Admins may update them.
2767
3155
  *
2768
3156
  * ```typescript
2769
- * import {getWebhooks} from '@verdocs/js-sdk';
3157
+ * import {getGroups} from '@verdocs/js-sdk';
2770
3158
  *
2771
- * await getWebhooks(ORGID, params);
3159
+ * const groups = await getGroups();
2772
3160
  * ```
2773
- *
2774
- * @group Webhooks
2775
- * @api GET /v2/webhooks Get organization Webhooks config
2776
- * @apiSuccess IWebhook . The current Webhooks config for the caller's organization.
2777
3161
  */
2778
- const getWebhooks = (endpoint) => endpoint.api //
2779
- .get(`/v2/webhooks`)
3162
+ const getGroups = (endpoint) => endpoint.api //
3163
+ .get(`/v2/organization-groups`)
2780
3164
  .then((r) => r.data);
2781
3165
  /**
2782
- * Update the registered Webhook configuration for the caller's organization. Note that
2783
- * Webhooks cannot currently be deleted, but may be easily disabled by setting `active`
2784
- * to `false` and/or setting the `url` to an empty string.
3166
+ * Get the details for a group, including its member profiles and list of permissions.
2785
3167
  *
2786
3168
  * ```typescript
2787
- * import {setWebhooks} from '@verdocs/js-sdk';
3169
+ * import {getGroup} from '@verdocs/js-sdk/v2/organization-groups';
2788
3170
  *
2789
- * await setWebhooks(ORGID, params);
3171
+ * const group = await getGroup(GROUPID);
2790
3172
  * ```
2791
- *
2792
- * @group Webhooks
2793
- * @api PATCH /v2/webhooks Update organization Webhooks config
2794
- * @apiDescription Note that Webhooks cannot currently be deleted, but may be easily disabled by setting `active` to `false` and/or setting the `url` to an empty string.
2795
- * @apiBody string url URL to send Webhook events to. An empty or invalid URL will disable Webhook calls.
2796
- * @apiBody boolean active Set to true to enable Webhooks calls.
2797
- * @apiBody object events Record<TWebhookEvent, boolean> map of events to enable/disable.
2798
- * @apiSuccess IWebhook . The updated Webhooks config for the caller's organization.
2799
3173
  */
2800
- const setWebhooks = (endpoint, params) => endpoint.api //
2801
- .patch(`/v2/webhooks`, params)
3174
+ const getGroup = (endpoint, groupId) => endpoint.api //
3175
+ .get(`/v2/organization-groups/${groupId}`)
2802
3176
  .then((r) => r.data);
2803
-
2804
- /**
2805
- * A map of the permissions each role confers.
2806
- */
2807
- const RolePermissions = {
2808
- owner: [
2809
- 'template:creator:create:public',
2810
- 'template:creator:create:org',
2811
- 'template:creator:create:personal',
2812
- 'template:creator:delete',
2813
- 'template:creator:visibility',
2814
- 'template:member:read',
2815
- 'template:member:write',
2816
- 'template:member:delete',
2817
- 'template:member:visibility',
2818
- 'owner:add',
2819
- 'owner:remove',
2820
- 'admin:add',
2821
- 'admin:remove',
2822
- 'member:view',
2823
- 'member:add',
2824
- 'member:remove',
2825
- 'org:create',
2826
- 'org:view',
2827
- 'org:update',
2828
- 'org:delete',
2829
- 'org:transfer',
2830
- 'org:list',
2831
- 'envelope:create',
2832
- 'envelope:cancel',
2833
- 'envelope:view',
2834
- ],
2835
- admin: [
2836
- 'template:creator:create:public',
2837
- 'template:creator:create:org',
2838
- 'template:creator:create:personal',
2839
- 'template:creator:delete',
2840
- 'template:creator:visibility',
2841
- 'template:member:read',
2842
- 'template:member:write',
2843
- 'template:member:delete',
2844
- 'template:member:visibility',
2845
- 'admin:add',
2846
- 'admin:remove',
2847
- 'member:view',
2848
- 'member:add',
2849
- 'member:remove',
2850
- 'org:create',
2851
- 'org:view',
2852
- 'org:update',
2853
- 'org:list',
2854
- 'envelope:create',
2855
- 'envelope:cancel',
2856
- 'envelope:view',
2857
- ],
2858
- member: [
2859
- 'template:creator:create:public',
2860
- 'template:creator:create:org',
2861
- 'template:creator:create:personal',
2862
- 'template:creator:delete',
2863
- 'template:creator:visibility',
2864
- 'template:member:read',
2865
- 'template:member:write',
2866
- 'template:member:delete',
2867
- 'member:view',
2868
- 'org:create',
2869
- 'org:view',
2870
- 'org:list',
2871
- 'envelope:create',
2872
- 'envelope:cancel',
2873
- 'envelope:view',
2874
- ],
2875
- basic_user: ['template:member:read', 'member:view', 'org:view', 'org:list'],
2876
- contact: ['org:view', 'org:list', 'org:create'],
2877
- };
2878
3177
  /**
2879
- * Confirm whether the user has all of the specified permissions.
3178
+ * Create a group. Note that "everyone" is a reserved name and may not be created.
3179
+ *
3180
+ * ```typescript
3181
+ * import {createGroup} from '@verdocs/js-sdk';
3182
+ *
3183
+ * const group = await createGroup(VerdocsEndpoint.getDefault(), {name:'newgroup'});
3184
+ * ```
2880
3185
  */
2881
- const userHasPermissions = (profile, permissions) => {
2882
- // No need to de-dupe here, we're just checking present-at-least-once set membership.
2883
- const netPermissions = [...(profile?.permissions || [])];
2884
- (profile?.roles || []).forEach((role) => {
2885
- netPermissions.push(...(RolePermissions[role] || []));
2886
- });
2887
- (profile?.group_profiles || []).forEach((groupProfile) => {
2888
- netPermissions.push(...(groupProfile.group?.permissions || []));
2889
- });
2890
- return permissions.every((perm) => netPermissions.includes(perm));
2891
- };
2892
-
2893
- const canPerformTemplateAction = (profile, action, template) => {
2894
- if (!template && !action.includes('create')) {
2895
- return { canPerform: false, message: 'Missing required template object' };
2896
- }
2897
- // We use BOGUS here to force the option-chain in things like template?.profile_id to NOT match profile?.profile_id because if both
2898
- // were undefined, they would actually match.
2899
- const profile_id = profile?.id || 'BOGUS';
2900
- const organization_id = profile?.organization_id || 'BOGUS';
2901
- const isCreator = template?.profile_id === profile_id;
2902
- const isSameOrg = template?.organization_id === organization_id;
2903
- const isPersonal = template?.is_personal ?? false;
2904
- const isPublic = template?.is_public ?? false;
2905
- const permissionsRequired = [];
2906
- switch (action) {
2907
- case 'create_personal':
2908
- permissionsRequired.push('template:creator:create:personal');
2909
- break;
2910
- case 'create_org':
2911
- permissionsRequired.push('template:creator:create:org');
2912
- break;
2913
- case 'create_public':
2914
- permissionsRequired.push('template:creator:create:public');
2915
- break;
2916
- case 'read':
2917
- if (!isCreator) {
2918
- if ((!isPersonal && isSameOrg) || !isPublic) {
2919
- permissionsRequired.push('template:member:read');
2920
- }
2921
- }
2922
- break;
2923
- case 'write':
2924
- if (!isCreator) {
2925
- permissionsRequired.push('template:member:read');
2926
- permissionsRequired.push('template:member:write');
2927
- }
2928
- break;
2929
- case 'change_visibility_personal':
2930
- if (isCreator) {
2931
- permissionsRequired.push('template:creator:create:personal');
2932
- }
2933
- else {
2934
- permissionsRequired.push('template:member:visibility');
2935
- }
2936
- break;
2937
- case 'change_visibility_org':
2938
- if (isCreator) {
2939
- permissionsRequired.push('template:creator:create:org');
2940
- }
2941
- else {
2942
- permissionsRequired.push('template:member:visibility');
2943
- }
2944
- break;
2945
- case 'change_visibility_public':
2946
- if (isCreator) {
2947
- permissionsRequired.push('template:creator:create:public');
2948
- permissionsRequired.push('template:creator:visibility');
2949
- }
2950
- else {
2951
- permissionsRequired.push('template:member:visibility');
2952
- }
2953
- break;
2954
- case 'delete':
2955
- if (isCreator) {
2956
- permissionsRequired.push('template:creator:delete');
2957
- }
2958
- else {
2959
- permissionsRequired.push('template:member:delete');
2960
- }
2961
- break;
2962
- default:
2963
- return { canPerform: false, message: 'Action is not defined' };
2964
- }
2965
- if (hasRequiredPermissions(profile, permissionsRequired)) {
2966
- return { canPerform: true, message: '' };
2967
- }
2968
- return { canPerform: false, message: `Insufficient access to perform '${action}'. Needed permissions: ${permissionsRequired.toString()}` };
2969
- };
2970
- const hasRequiredPermissions = (profile, permissions) => permissions.every((perm) => (profile?.permissions || []).includes(perm));
2971
-
3186
+ const createGroup = (endpoint, params) => endpoint.api //
3187
+ .post('/v2/organization-groups', params)
3188
+ .then((r) => r.data);
2972
3189
  /**
2973
- * Add a field to a template.
3190
+ * Update a group. Note that "everyone" is a reserved name and may not be changed.
2974
3191
  *
2975
3192
  * ```typescript
2976
- * import {createField} from '@verdocs/js-sdk/Templates';
3193
+ * import {updateGroup} from '@verdocs/js-sdk';
2977
3194
  *
2978
- * await createField((VerdocsEndpoint.getDefault(), template_id, { ... });
3195
+ * const updated = await updateGroup(VerdocsEndpoint.getDefault(), {name:'newname'});
2979
3196
  * ```
3197
+ */
3198
+ const updateGroup = (endpoint, groupId, params) => endpoint.api //
3199
+ .patch(`/v2/organization-groups/${groupId}`, params)
3200
+ .then((r) => r.data);
3201
+ /**
3202
+ * Get an organization by ID. Note that the "everyone" group cannot be deleted.
2980
3203
  *
2981
- * @group Fields
2982
- * @api POST /v2/fields/:template_id Add a field to a template
2983
- * @apiBody string name Name for the new field. Field names must be unique within a template. Although special characters are allowed, they must be URL-encoded in subsequent requests, so it is recommended to use only alphanumeric characters and hyphens if possible.
2984
- * @apiBody string role_name Role to assign to the field. Role names must be valid, so it is recommended to create roles before fields.
2985
- * @apiBody string document_id ID of the document upon which to place the field.
2986
- * @apiBody string(enum: 'signature' | 'initial' | 'checkbox' | 'radio' | 'textbox' | 'timestamp' | 'date' | 'dropdown' | 'textarea' | 'attachment' | 'payment') type Type of field to create
2987
- * @apiBody boolean(default: false) required Whether the field is required
2988
- * @apiBody integer(min: 0) page 0-based page number upon which to place the field
2989
- * @apiBody integer(min: 0) x X position for the field (left to right)
2990
- * @apiBody integer(min: 0) y Y position for the field (_bottom to top!_)
2991
- * @apiBody string label? Optional label to display above the field
2992
- * @apiBody integer(min: 50) width? Width of the field. Note that all fields have built-in defaults, and it is recommended that this only be set on text fields.
2993
- * @apiBody integer(min: 15) height? Height of the field. Note that all fields have built-in defaults, and it is recommended that this only be set on text fields.
2994
- * @apiBody string placeholder? Optional placeholder to display in text fields
2995
- * @apiBody string group? For fields that support grouping (radio buttons and check boxes) the value selected will be stored under this name
2996
- * @apiBody array(items:IDropdownOption) options? For dropdown fields, the options to display
2997
- * @apiBody string value? Optional default value to set on the field
2998
- * @apiSuccess ITemplateField . Template field
3204
+ * ```typescript
3205
+ * import {deleteGroup} from '@verdocs/js-sdk';
3206
+ *
3207
+ * await deleteGroup(VerdocsEndpoint.getDefault(), 'ORGID');
3208
+ * ```
2999
3209
  */
3000
- const createField = (endpoint, templateId, params) => endpoint.api //
3001
- .post(`/v2/fields/${templateId}`, params)
3210
+ const deleteGroup = (endpoint, groupId) => endpoint.api //
3211
+ .delete(`/v2/organization-groups/${groupId}`)
3002
3212
  .then((r) => r.data);
3003
3213
  /**
3004
- * Update a template field.
3214
+ * Add a member to a group.
3005
3215
  *
3006
3216
  * ```typescript
3007
- * import {updateField} from '@verdocs/js-sdk/Templates';
3217
+ * import {addGroupMember} from '@verdocs/js-sdk';
3008
3218
  *
3009
- * await updateField((VerdocsEndpoint.getDefault(), template_id, field_name, { ... });
3219
+ * await addGroupMember(VerdocsEndpoint.getDefault(), 'GROUPID', 'PROFILEID');
3010
3220
  * ```
3011
- *
3012
- * @group Fields
3013
- * @api PATCH /v2/fields/:template_id/:field_name Update a field. See createField for additional details on the supported parameters.
3014
- * @apiBody string name? Rename the field. Note that template field names must be unique within a template.
3015
- * @apiBody string role_name Role to assign to the field.
3016
- * @apiBody string document_id ID of the document upon which to place the field.
3017
- * @apiBody string(enum: 'signature' | 'initial' | 'checkbox' | 'radio' | 'textbox' | 'timestamp' | 'date' | 'dropdown' | 'textarea' | 'attachment' | 'payment') type? Change the field type. Note that while this is technically allowed, fields have different behaviors, validators, default sizes, etc. It is usually easier to add a new field and delete the old one.
3018
- * @apiBody boolean(default: false) required? Whether the field is required
3019
- * @apiBody integer(min: 0) page? 0-based page number upon which to place the field
3020
- * @apiBody integer(min: 0) x? X position for the field (left to right)
3021
- * @apiBody integer(min: 0) y? Y position for the field (_bottom to top!_)
3022
- * @apiBody string label? Optional label to display above the field
3023
- * @apiBody integer(min: 50) width? Width of the field. Note that all fields have built-in defaults, and it is recommended that this only be set on text fields.
3024
- * @apiBody integer(min: 15) height? Height of the field. Note that all fields have built-in defaults, and it is recommended that this only be set on text fields.
3025
- * @apiBody string placeholder? Optional placeholder to display in text fields
3026
- * @apiBody string group? For fields that support grouping (radio buttons and check boxes) the value selected will be stored under this name
3027
- * @apiBody array(items:IDropdownOption) options? For dropdown fields, the options to display
3028
- * @apiBody string value? Optional default value to set on the field
3029
- * @apiSuccess ITemplateField . Updated template field
3030
3221
  */
3031
- const updateField = (endpoint, templateId, name, params) => endpoint.api //
3032
- .patch(`/v2/fields/${templateId}/${encodeURIComponent(name)}`, params)
3222
+ const addGroupMember = (endpoint, groupId, profile_id) => endpoint.api //
3223
+ .post(`/v2/organization-groups/${groupId}/members`, { profile_id })
3033
3224
  .then((r) => r.data);
3034
3225
  /**
3035
- * Remove a field from a template.
3226
+ * Remove a member from a group.
3036
3227
  *
3037
3228
  * ```typescript
3038
- * import {deleteField} from '@verdocs/js-sdk/Templates';
3229
+ * import {deleteGroupMember} from '@verdocs/js-sdk';
3039
3230
  *
3040
- * await deleteField((VerdocsEndpoint.getDefault(), template_id, field_name);
3231
+ * await deleteGroupMember(VerdocsEndpoint.getDefault(), 'GROUPID', 'PROFILEID');
3041
3232
  * ```
3042
- *
3043
- * @group Fields
3044
- * @api DELETE /v2/fields/:template_id/:field_name Delete a field
3045
- * @apiSuccess string . Success
3046
3233
  */
3047
- const deleteField = (endpoint, templateId, name) => endpoint.api //
3048
- .delete(`/v2/fields/${templateId}/${encodeURIComponent(name)}`)
3234
+ const deleteGroupMember = (endpoint, groupId, profile_id) => endpoint.api //
3235
+ .delete(`/v2/organization-groups/${groupId}/members/${profile_id}`)
3049
3236
  .then((r) => r.data);
3050
3237
 
3051
3238
  /**
3052
- * Various helpers to identify available operations for a template by a user.
3239
+ * An invitation represents an opportunity for a Member to join an Organization.
3053
3240
  *
3054
3241
  * @module
3055
3242
  */
3056
3243
  /**
3057
- * Check to see if the user created the template.
3058
- */
3059
- const userIsTemplateCreator = (profile, template) => profile && template && profile.id === template.profile_id;
3060
- /**
3061
- * Check to see if a template is "shared" with the user.
3062
- */
3063
- const userHasSharedTemplate = (profile, template) => profile && template && !template.is_personal && profile.organization_id === template.organization_id;
3064
- /**
3065
- * Check to see if the user can create a personal/private template.
3066
- */
3067
- const userCanCreatePersonalTemplate = (profile) => userHasPermissions(profile, ['template:creator:create:personal']);
3068
- /**
3069
- * Check to see if the user can create an org-shared template.
3070
- */
3071
- const userCanCreateOrgTemplate = (profile) => userHasPermissions(profile, ['template:creator:create:org']);
3072
- /**
3073
- * Check to see if the user can create a public template.
3074
- */
3075
- const userCanCreatePublicTemplate = (profile) => userHasPermissions(profile, ['template:creator:create:public']);
3076
- /**
3077
- * Check to see if the user can read/view a template.
3078
- */
3079
- const userCanReadTemplate = (profile, template) => template.is_public ||
3080
- userIsTemplateCreator(profile, template) ||
3081
- (userHasSharedTemplate(profile, template) && userHasPermissions(profile, ['template:member:read']));
3082
- /**
3083
- * Check to see if the user can update a tempate.
3084
- */
3085
- const userCanUpdateTemplate = (profile, template) => userIsTemplateCreator(profile, template) ||
3086
- (userHasSharedTemplate(profile, template) && userHasPermissions(profile, ['template:member:read', 'template:member:write']));
3087
- /**
3088
- * Check to see if the user can change whether a template is personal vs org-shared.
3089
- */
3090
- const userCanMakeTemplatePrivate = (profile, template) => userIsTemplateCreator(profile, template)
3091
- ? userHasPermissions(profile, ['template:creator:create:personal'])
3092
- : userHasPermissions(profile, ['template:member:visibility']);
3093
- /**
3094
- * Check to see if the user can change whether a template is personal vs org-shared.
3244
+ * Get a list of invitations pending for the caller's organization. The caller must be an admin or owner.
3245
+ *
3246
+ * @group Organization Invitations
3247
+ * @api GET /v2/organization-invitations Get a list of pending invitations
3248
+ * @apiBody array(items:TRole) roles URL to send Webhook events to. An empty or invalid URL will disable Webhook calls.
3249
+ * @apiBody string first_name First name. The user may override this after accepting the invitation.
3250
+ * @apiBody string last_name Last name. The user may override this after accepting the invitation.
3251
+ * @apiSuccess array(items:IProfile) . List of caller's current organization's members
3095
3252
  */
3096
- const userCanMakeTemplateShared = (profile, template) => userIsTemplateCreator(profile, template)
3097
- ? userHasPermissions(profile, ['template:creator:create:org'])
3098
- : userHasPermissions(profile, ['template:member:visibility']);
3253
+ const getOrganizationInvitations = (endpoint) => endpoint.api //
3254
+ .get(`/v2/organization-invitations`)
3255
+ .then((r) => r.data);
3099
3256
  /**
3100
- * Check to see if the user can change whether a template is personal vs org-shared.
3257
+ * Invite a new user to join the organization.
3258
+ *
3259
+ * @group Organization Invitations
3260
+ * @api POST /v2/organization-invitations Invite a new user to join the organization
3261
+ * @apiBody string email Email address to send the invitation to
3262
+ * @apiBody string first_name First name. The user may override this after accepting the invitation.
3263
+ * @apiBody string last_name Last name. The user may override this after accepting the invitation.
3264
+ * @apiBody TRole role Initial role to assign to the user once they accept.
3265
+ * @apiSuccess IOrganizationInvitation . The newly-created invitation.
3101
3266
  */
3102
- const userCanMakeTemplatePublic = (profile, template) => userIsTemplateCreator(profile, template)
3103
- ? userHasPermissions(profile, ['template:creator:create:public'])
3104
- : userHasPermissions(profile, ['template:member:visibility']);
3267
+ const createOrganizationInvitation = (endpoint, params) => endpoint.api //
3268
+ .post(`/v2/organization-invitations`, params)
3269
+ .then((r) => r.data);
3105
3270
  /**
3106
- * Check to see if the user can change whether a template is personal vs org-shared.
3271
+ * Delete an invitation. Note that no cancellation message will be sent. Invitations are also one-time-use.
3272
+ * If the invitee attempts to join after the invitation is deleted, accepted, or decline, they will be
3273
+ * shown an error.
3274
+ *
3275
+ * @group Organization Invitations
3276
+ * @api DELETE /v2/organization-invitations/:email Delete a pending invitation
3277
+ * @apiSuccess string . Success
3107
3278
  */
3108
- const userCanChangeOrgVisibility = (profile, template) => userIsTemplateCreator(profile, template) && userHasPermissions(profile, ['template:creator:create:personal']);
3279
+ const deleteOrganizationInvitation = (endpoint, email) => endpoint.api //
3280
+ .delete(`/v2/organization-invitations/${email}`)
3281
+ .then((r) => r.data);
3109
3282
  /**
3110
- * Check to see if the user can change whether a template is personal vs org-shared.
3283
+ * Update an invitation. Note that email may not be changed after the invite is sent. To change
3284
+ * an invitee's email, delete the incorrect entry and create one with the correct value.
3285
+ *
3286
+ * @group Organization Invitations
3287
+ * @api PATCH /v2/organization-invitations/:email Update a pending invitation
3288
+ * @apiBody string first_name First name. The user may override this after accepting the invitation.
3289
+ * @apiBody string last_name Last name. The user may override this after accepting the invitation.
3290
+ * @apiBody TRole role Initial role to assign to the user once they accept.
3291
+ * @apiSuccess IOrganizationInvitation . The updated invitation.
3111
3292
  */
3112
- const userCanDeleteTemplate = (profile, template) => userIsTemplateCreator(profile, template)
3113
- ? userHasPermissions(profile, ['template:creator:delete'])
3114
- : userHasPermissions(profile, ['template:member:delete']);
3293
+ const updateOrganizationInvitation = (endpoint, email, params) => endpoint.api //
3294
+ .patch(`/v2/organization-invitations/${email}`, params)
3295
+ .then((r) => r.data);
3115
3296
  /**
3116
- * Confirm whether the user can create an envelope using the specified template.
3297
+ * Send a reminder to the invitee to join the organization.
3298
+ *
3299
+ * @group Organization Invitations
3300
+ * @api POST /v2/organization-invitations/resend Send a reminder to a pending invitee
3301
+ * @apiBody string email The recipient to send the reminder to
3302
+ * @apiSuccess IOrganizationInvitation . The updated invitation
3117
3303
  */
3118
- const userCanSendTemplate = (profile, template) => {
3119
- switch (template.visibility) {
3120
- case 'private':
3121
- return userIsTemplateCreator(profile, template);
3122
- case 'shared':
3123
- return userIsTemplateCreator(profile, template) || template.organization_id === profile?.organization_id;
3124
- case 'public':
3125
- return true;
3126
- }
3127
- };
3304
+ const resendOrganizationInvitation = (endpoint, email) => endpoint.api //
3305
+ .post('/v2/organization-invitations/resend', { email })
3306
+ .then((r) => r.data);
3128
3307
  /**
3129
- * Confirm whether the user can create a new template.
3308
+ * Get an invitation's details. This is generally used as the first step of accepting the invite.
3309
+ * A successful response will indicate that the invite token is still valid, and include some
3310
+ * metadata for the organization to style the acceptance screen.
3311
+ *
3312
+ * @group Organization Invitations
3313
+ * @api GET /v2/organization-invitations/:email/:token Get a pending invitation (_Authenticated via invite token, not an active session._). Intended to be called by the invitee to get details about the invitation they are about to accept.
3314
+ * @apiSuccess IOrganizationInvitation . Requested invitation's details. Will always include summary details for the organization, to be used for branding the accept-invite view.
3130
3315
  */
3131
- const userCanCreateTemplate = (profile) => userCanCreatePersonalTemplate(profile) || userCanCreateOrgTemplate(profile) || userCanCreatePublicTemplate(profile);
3316
+ const getOrganizationInvitation = (endpoint, email, token) => endpoint.api //
3317
+ .get(`/v2/organization-invitations/${email}/${token}`)
3318
+ .then((r) => r.data);
3132
3319
  /**
3133
- * Check to see if the user can "build" the template (use the field builder). The user must have write access to the
3134
- * template, and the template must have at least one signer role.
3320
+ * Accept an invitation. This will automatically create a user record for the caller as well as a profile
3321
+ * with the appropriate role as specified in the invite. The profile will be set as "current" for the caller,
3322
+ * and session tokens will be returned to access the new profile. The profile's email_verified flag will
3323
+ * also be set to true.
3324
+ *
3325
+ * @group Organization Invitations
3326
+ * @api POST /v2/organization-invitations/accept Accept an invitation
3327
+ * @apiBody string email Email address for the invitee
3328
+ * @apiBody string token Invite token for the invitee
3329
+ * @apiBody string first_name First name
3330
+ * @apiBody string last_name Last name
3331
+ * @apiBody string password Password
3332
+ * @apiSuccess IAuthenticateResponse . Session credentials for the newly-created user's profile. If the user already had a profile for another organization, the new profile will be made "current" automatically.
3135
3333
  */
3136
- const userCanBuildTemplate = (profile, template) => userCanUpdateTemplate(profile, template) && (template.roles || []).filter((role) => role.type === 'signer').length > 0;
3137
- const getFieldsForRole = (template, role_name) => (template.fields || []).filter((field) => field.role_name === role_name);
3334
+ const acceptOrganizationInvitation = (endpoint, params) => endpoint.api //
3335
+ .post('/v2/organization-invitations/accept', params)
3336
+ .then((r) => r.data);
3138
3337
  /**
3139
- * Check to see if the user can preview the template. The user must have read access to the template, the template must
3140
- * have at least one signer, and every signer must have at least one field.
3338
+ * Decline an invitation. This will mark the status "declined," providing a visual indication to the
3339
+ * organization's admins that the invite was declined, preventing further invites from being created
3340
+ * to the same email address, and also preventing the invitee from receiving reminders to join.
3341
+ *
3342
+ * @group Organization Invitations
3343
+ * @api POST /v2/organization-invitations/decline Decline an invitation
3344
+ * @apiDescription Mark the status "declined," providing a visual indication to the organization's admins that the invite was declined, preventing further invites from being created to the same email address, and also preventing the invitee from receiving reminders to join.
3345
+ * @apiBody string email Email address for the invitee
3346
+ * @apiBody string token Invite token for the invitee
3347
+ * @apiSuccess string . Success. The invitation will be marked declined and the token will be invalidated.
3141
3348
  */
3142
- const userCanPreviewTemplate = (profile, template) => {
3143
- const hasPermission = userCanReadTemplate(profile, template);
3144
- const signers = (template.roles || []).filter((role) => role.type === 'signer');
3145
- return hasPermission && signers.length > 0 && signers.every((signer) => getFieldsForRole(template, signer.name).length > 0);
3146
- };
3349
+ const declineOrganizationInvitation = (endpoint, email, token) => endpoint.api //
3350
+ .post('/v2/organization-invitations/decline', { email, token })
3351
+ .then((r) => r.data);
3147
3352
 
3148
3353
  /**
3149
- * A "role" is an individual participant in a signing flow, such as a signer or CC contact.
3150
- * A role is a placeholder that will eventually become a named recipient. For example, "Tenant 1"
3151
- * might be replaced with "John Smith" when the document is sent out for signature.
3152
- *
3153
- * Role names must be unique within a template, e.g. 'Recipient 1'. They may contain any [a-zA-Z0-9_- ]
3154
- * characters, although it is recommended to keep them simple and human-readable, and to avoid
3155
- * spaces (although they are allowed). If spaces are used in role names, be sure to URL-encode them
3156
- * when calling endpoints like `updateRole()` e.g. 'Recipient%201'.
3157
- *
3158
- * NOTE: Roles are always enumerated under Template objects, so there are no "list" or "get" endpoints
3159
- * for them. To get a template's latest role list, simply call `getTemplate()`.
3354
+ * An Organization Member (aka Profile) is an individual user with access to an organization.
3160
3355
  *
3161
3356
  * @module
3162
3357
  */
3163
3358
  /**
3164
- * Create a role.
3165
- *
3166
- * ```typescript
3167
- * import {createTemplateRole} from '@verdocs/js-sdk';
3168
- *
3169
- * const role = await createTemplateRole(VerdocsEndpoint.getDefault(), template_id, params...);
3170
- * ```
3171
- *
3172
- * @group Roles
3173
- * @api POST /v2/roles/:template_id Add a role to a template
3174
- * @apiBody string name Name for the new role. Must be unique within the template. May include spaces, but later calls must URL-encode any references to this role, so it is recomended that special characters be avoided.
3175
- * @apiBody string(enum:'signer' | 'cc' | 'approver') type Type of role to create. Signers act on documents by filling and signing fields. CC recipients receive a copy but do not act on the document. Approvers control the final submission of a document, but do not have fields of their own to fill out.
3176
- * @apiBody string full_name? Default full name for the role. May be completed/overridden later, when envelopes are made from the template.
3177
- * @apiBody string email? Default email address for the role. May be completed/overridden later, when envelopes are made from the template.
3178
- * @apiBody string phone? Default (SMS-capable) phone number for the role. May be completed/overridden later, when envelopes are made from the template.
3179
- * @apiBody string message? Optional message to include in email and SMS signing invitations.
3180
- * @apiBody integer(min: 1, default: 1) sequence? Optional 1-based sequence number for the role. Roles that share the same sequence number act in parallel, and will receive invitations at the same time.
3181
- * @apiBody integer(min: 1, default: 1) order? Optional 1-based order number for the role. Controls the left-to-right display order of roles at the same sequence number in the UI components e.g. `<verdocs-template-roles />`.
3182
- * @apiBody boolean delegator? If true, the role may delegate their signing responsibility to another party.
3183
- * @apiSuccess IRole . The newly-created role
3184
- */
3185
- const createTemplateRole = (endpoint, template_id, params) => endpoint.api //
3186
- .post(`/v2/roles/${template_id}`, params)
3187
- .then((r) => r.data);
3188
- /**
3189
- * Update a role.
3359
+ * Get a list of the members in the caller's organization.
3190
3360
  *
3191
3361
  * ```typescript
3192
- * import {updateTemplateRole} from '@verdocs/js-sdk';
3362
+ * import {getOrganizationMembers} from '@verdocs/js-sdk';
3193
3363
  *
3194
- * const role = await updateTemplateRole(VerdocsEndpoint.getDefault(), template_id, name, params...);
3364
+ * const members = await getOrganizationMembers(VerdocsEndpoint.getDefault()});
3195
3365
  * ```
3196
3366
  *
3197
- * @group Roles
3198
- * @api PATCH /v2/roles/:template_id/:role_id Update a role. See createRole for additional details on the parameters available.
3199
- * @apiBody string name? Rename the role. Note that role names must be unique within a template, so this may fail if the new name is already in use.
3200
- * @apiBody string(enum:'signer' | 'cc' | 'approver') type? Type of role.
3201
- * @apiBody string full_name? Default full name for the role.
3202
- * @apiBody string email? Default email address for the role.
3203
- * @apiBody string phone? Default (SMS-capable) phone number for the role.
3204
- * @apiBody string message? Optional message to include in email and SMS signing invitations.
3205
- * @apiBody integer(min: 1, default: 1) sequence? Optional 1-based sequence number for the role.
3206
- * @apiBody integer(min: 1, default: 1) order? Optional 1-based order number for the role.
3207
- * @apiBody boolean delegator? If true, the role may delegate their signing responsibility to another party.
3208
- * @apiBody string(enum:'pin'|'identity'|'') kba_method? Active PIN- or Identity-based KBA for the role.
3209
- * @apiSuccess IRole . The newly-created role
3367
+ * @group Organization Members
3368
+ * @api GET /v2/organization-members List current organization's members
3369
+ * @apiSuccess array(items:IProfile) . List of caller's current organization's members
3210
3370
  */
3211
- const updateTemplateRole = (endpoint, template_id, name, params) => endpoint.api //
3212
- .patch(`/v2/roles/${template_id}/${encodeURIComponent(name)}`, params)
3371
+ const getOrganizationMembers = (endpoint) => endpoint.api //
3372
+ .get(`/v2/organization-members`)
3213
3373
  .then((r) => r.data);
3214
3374
  /**
3215
- * Delete a role.
3375
+ * Delete a member from the caller's organization. Note that the caller must be an admin or owner,
3376
+ * may not delete him/herself.
3216
3377
  *
3217
3378
  * ```typescript
3218
- * import {deleteTemplateRole} from '@verdocs/js-sdk';
3379
+ * import {deleteOrganizationMember} from '@verdocs/js-sdk';
3219
3380
  *
3220
- * const profiles = await deleteTemplateRole(VerdocsEndpoint.getDefault(), template_id, name);
3381
+ * await deleteOrganizationMember(VerdocsEndpoint.getDefault(), 'PROFILEID'});
3221
3382
  * ```
3222
3383
  *
3223
- * @group Roles
3224
- * @api DELETE /v2/roles/:template_id/:role_id Delete a role.
3384
+ * @group Organization Members
3385
+ * @api DELETE /v2/organization-members/:profile_id Delete a member from the organization
3225
3386
  * @apiSuccess string . Success
3226
3387
  */
3227
- const deleteTemplateRole = (endpoint, template_id, name) => endpoint.api //
3228
- .delete(`/v2/roles/${template_id}/${encodeURIComponent(name)}`)
3388
+ const deleteOrganizationMember = (endpoint, profileId) => endpoint.api //
3389
+ .delete(`/v2/organization-members/${profileId}`)
3229
3390
  .then((r) => r.data);
3230
-
3231
- /**
3232
- * A Template defines how a Verdocs signing flow will be performed, including attachments, signing fields, and
3233
- * recipients.
3234
- *
3235
- * @module
3236
- */
3237
3391
  /**
3238
- * Get all templates accessible by the caller, with optional filters.
3392
+ * Update a member.
3239
3393
  *
3240
3394
  * ```typescript
3241
- * import {getTemplates} from '@verdocs/js-sdk/Templates';
3395
+ * import {updateOrganizationMember} from '@verdocs/js-sdk';
3242
3396
  *
3243
- * await getTemplates((VerdocsEndpoint.getDefault());
3244
- * await getTemplates((VerdocsEndpoint.getDefault(), { is_starred: true });
3245
- * await getTemplates((VerdocsEndpoint.getDefault(), { is_creator: true });
3246
- * await getTemplates((VerdocsEndpoint.getDefault(), { is_organization: true });
3397
+ * const result = await updateOrganizationMember(VerdocsEndpoint.getDefault(), 'PROFILEID', {roles:['member']});
3247
3398
  * ```
3248
3399
  *
3249
- * @group Templates
3250
- * @api GET /v2/templates Get Templates
3251
- * @apiQuery string q? Find templates whose names/descriptions contain the specified query string
3252
- * @apiQuery boolean is_starred? If true, returns only templates with at least one "star".
3253
- * @apiQuery boolean is_creator? If true, returns only templates that the caller created.
3254
- * @apiQuery string(enum: 'private_shared' | 'private' | 'shared' | 'public') visibility? Return only templates with the specified visibility.
3255
- * @apiQuery string(enum: 'created_at' | 'updated_at' | 'name' | 'last_used_at' | 'counter' | 'star_counter') sort_by? Return results sorted by this criteria
3256
- * @apiQuery boolean ascending? Set true/false to override the sort direction. Note that the default depends on `sort_by`. Date-based sorts default to descending, while name defaults to ascending.
3257
- * @apiQuery integer(default: 20) rows? Limit the number of rows returned
3258
- * @apiQuery integer(default: 0) page? Specify which page of results to return
3259
- * @apiSuccess integer(format: int32) count The total number of records matching the query, helpful for pagination
3260
- * @apiSuccess integer(format: int32) rows The number of rows returned in this response page
3261
- * @apiSuccess integer(format: int32) page The page number of this response
3262
- * @apiSuccess array(items: ITemplate) templates List of templates found
3400
+ * @group Organization Members
3401
+ * @api PATCH /v2/organization-members/:profile_id Update an organization member.
3402
+ * @apiBody array(items:TRole) roles URL to send Webhook events to. An empty or invalid URL will disable Webhook calls.
3403
+ * @apiBody string first_name Set to true to enable Webhooks calls.
3404
+ * @apiBody string last_name Record<TWebhookEvent, boolean> map of events to enable/disable.
3405
+ * @apiSuccess array(items:IProfile) . List of caller's current organization's members
3263
3406
  */
3264
- const getTemplates = (endpoint, params) => endpoint.api //
3265
- .get('/v2/templates', { params })
3407
+ const updateOrganizationMember = (endpoint, profileId, params) => endpoint.api //
3408
+ .patch(`/v2/organization-members/${profileId}`, params)
3266
3409
  .then((r) => r.data);
3410
+
3267
3411
  /**
3268
- * Get one template by its ID.
3412
+ * An Organization is the top level object for ownership for Members, Documents, and Templates.
3413
+ *
3414
+ * NOTE: There is no call specifically to create an organization. Every organization must have
3415
+ * at least one "owner" type member. To create a new organization, call createProfile() with
3416
+ * the desired new orgName to create. The caller will become the first owner of the new org, and
3417
+ * can then invite new members to join as well.
3418
+ *
3419
+ * NOTE: There is no call to delete an organization. For safety, this is a manual process. Please
3420
+ * contact support@verdocs.com if you wish to completely delete an organization and all its records.
3421
+ *
3422
+ * @module
3423
+ */
3424
+ /**
3425
+ * Get an organization by ID. Note that this endpoint will return only a subset of fields
3426
+ * if the caller is not a member of the organization (the public fields).
3269
3427
  *
3270
3428
  * ```typescript
3271
- * import {getTemplate} from '@verdocs/js-sdk/Templates';
3429
+ * import {getOrganization} from '@verdocs/js-sdk';
3272
3430
  *
3273
- * const template = await getTemplate((VerdocsEndpoint.getDefault(), '83da3d70-7857-4392-b876-c4592a304bc9');
3431
+ * const organizations = await getOrganization(VerdocsEndpoint.getDefault(), 'ORGID');
3274
3432
  * ```
3275
3433
  *
3276
- * @group Templates
3277
- * @api GET /v2/templates/:template_id Get a template. Note that the caller must have at least View access to the template.
3278
- * @apiSuccess ITemplate . The requested template
3434
+ * @group Organizations
3435
+ * @api GET /v2/organizations/:organization_id Get organization
3436
+ * @apiSuccess IOrganization . The requested organization. The caller must be a member.
3279
3437
  */
3280
- const getTemplate = (endpoint, templateId) => {
3281
- return endpoint.api //
3282
- .get(`/v2/templates/${templateId}`)
3283
- .then((r) => {
3284
- const template = r.data;
3285
- // Post-process the template to upgrade to new data fields
3286
- if (!template.documents && template.template_documents) {
3287
- template.documents = template.template_documents;
3288
- }
3289
- template.documents?.forEach((document) => {
3290
- if (!document.order) {
3291
- document.order = 0;
3292
- }
3293
- if (document.page_numbers) {
3294
- document.pages = document.page_numbers;
3295
- }
3296
- });
3297
- // Temporary upgrade from legacy app
3298
- template.fields?.forEach((field) => {
3299
- if (field.setting) {
3300
- field.settings = field.setting;
3301
- }
3302
- });
3303
- return template;
3304
- });
3305
- };
3306
- const ALLOWED_CREATE_FIELDS = [
3307
- 'name',
3308
- 'is_personal',
3309
- 'is_public',
3310
- 'sender',
3311
- 'description',
3312
- 'roles',
3313
- 'fields',
3314
- ];
3438
+ const getOrganization = (endpoint, organizationId) => endpoint.api //
3439
+ .get(`/v2/organizations/${organizationId}`)
3440
+ .then((r) => r.data);
3315
3441
  /**
3316
- * Create a template.
3442
+ * Get an organization's "children".
3317
3443
  *
3318
3444
  * ```typescript
3319
- * import {createTemplate} from '@verdocs/js-sdk/Templates';
3445
+ * import {getOrganizationChildren} from '@verdocs/js-sdk';
3320
3446
  *
3321
- * const newTemplate = await createTemplate((VerdocsEndpoint.getDefault(), {...});
3447
+ * const children = await getOrganizationChildren(VerdocsEndpoint.getDefault(), 'ORGID');
3322
3448
  * ```
3323
3449
  *
3324
- * @group Templates
3325
- * @api POST /v2/templates Create a template
3326
- * @apiBody string name Template name
3327
- * @apiBody string description? Optional description
3328
- * @apiBody TTemplateVisibility visibility? Visibility setting
3329
- * @apiBody boolean is_personal? Deprecated. If true, the template is personal and can only be seen by the caller. (Use "visibility" for new calls.)
3330
- * @apiBody boolean is_public? Deprecated. If true, the template is public and can be seen by anybody. (Use "visibility" for new calls.)
3331
- * @apiBody TTemplateSender sender? Who may send envelopes using this template
3332
- * @apiBody number initial_reminder? Delay (in seconds) before the first reminder is sent (min: 4hrs). Set to 0 or null to disable.
3333
- * @apiBody number followup_reminders? Delay (in seconds) before the subsequent reminders are sent (min: 12hrs). Set to 0 or null to disable.
3334
- * @apiBody array(items:object) documents? Optional list of documents to attach to the template
3335
- * @apiBody array(items:IRole) roles? Optional list of roles to create. Note that if roles are not included in the request, fields will be ignored.
3336
- * @apiBody array(fields:ITemplateField) fields? Optional list of fields to create. Note that if fields that do not match a role will be ignored.
3337
- * @apiSuccess ITemplate . The newly-created template
3450
+ * @group Organizations
3451
+ * @api GET /v2/organizations/:organization_id/children Get an organization's children
3452
+ * @apiSuccess IOrganization[] . Any child organizations found.
3338
3453
  */
3339
- const createTemplate = (endpoint, params, onUploadProgress) => {
3340
- const options = {
3341
- timeout: 120000,
3342
- onUploadProgress: (event) => {
3343
- const total = event.total || 1;
3344
- const loaded = event.loaded || 0;
3345
- onUploadProgress?.(Math.floor((loaded * 100) / (total)), loaded, total);
3346
- },
3347
- };
3348
- if (params.documents && params.documents[0] instanceof File) {
3349
- const formData = new FormData();
3350
- ALLOWED_CREATE_FIELDS.forEach((allowedKey) => {
3351
- if (params[allowedKey] !== undefined) {
3352
- formData.append(allowedKey, params[allowedKey]);
3353
- }
3354
- });
3355
- params.documents.forEach((file) => {
3356
- formData.append('documents', file, file.name);
3357
- });
3358
- return endpoint.api.post('/v2/templates', formData, options).then((r) => r.data);
3359
- }
3360
- else {
3361
- return endpoint.api.post('/v2/templates', params, options).then((r) => r.data);
3362
- }
3363
- };
3454
+ const getOrganizationChildren = (endpoint, organizationId) => endpoint.api //
3455
+ .get(`/v2/organizations/${organizationId}/children`)
3456
+ .then((r) => r.data);
3364
3457
  /**
3365
- * Duplicate a template. Creates a complete clone, including all settings (e.g. reminders), fields,
3366
- * roles, and documents.
3458
+ * Get an organization's usage data. If the organization is a parent, usage data for children
3459
+ * will be included as well. The response will be a nested object keyed by organization ID,
3460
+ * with each entry being a dictionary of usageType:count entries.
3367
3461
  *
3368
3462
  * ```typescript
3369
- * import {duplicateTemplate} from '@verdocs/js-sdk/Templates';
3463
+ * import {getOrganizationUsage} from '@verdocs/js-sdk';
3370
3464
  *
3371
- * const newTemplate = await duplicateTemplate((VerdocsEndpoint.getDefault(), originalTemplateId, 'My Template Copy');
3465
+ * const usage = await getOrganizationUsage(VerdocsEndpoint.getDefault(), 'ORGID');
3372
3466
  * ```
3373
3467
  *
3374
- * @group Templates
3375
- * @api PUT /v2/templates/:template_id Perform an operation on a template
3376
- * @apiBody string(enum:'duplicate') action Action to perform
3377
- * @apiBody string name? If duplicating the template, a name for the new copy
3378
- * @apiSuccess ITemplate . The newly-copied template
3468
+ * @group Organizations
3469
+ * @api GET /v2/organizations/:organization_id/usage Get an organization's usage metrics
3470
+ * @apiSuccess TOrganizationUsage . Usage data grouped by organization ID
3379
3471
  */
3380
- const duplicateTemplate = (endpoint, templateId, name) => endpoint.api //
3381
- .put(`/v2/templates/${templateId}`, { action: 'duplicate', name })
3472
+ const getOrganizationUsage = (endpoint, organizationId, params) => endpoint.api //
3473
+ .get(`/v2/organizations/${organizationId}/usage`, { params })
3382
3474
  .then((r) => r.data);
3383
3475
  /**
3384
- * Create a template from a Sharepoint asset.
3476
+ * Create an organization. The caller will be assigned an "Owner" profile in the new organization,
3477
+ * and it will be set to "current" automatically. A new set of session tokens will be issued to
3478
+ * the caller, and the caller should update their endpoint to use the new tokens.
3385
3479
  *
3386
3480
  * ```typescript
3387
- * import {createTemplateFromSharepoint} from '@verdocs/js-sdk/Templates';
3481
+ * import {createOrganization} from '@verdocs/js-sdk';
3388
3482
  *
3389
- * const newTemplate = await createTemplateFromSharepoint((VerdocsEndpoint.getDefault(), {...});
3483
+ * const organization = await createOrganization(VerdocsEndpoint.getDefault(), {name: 'NewOrg'});
3390
3484
  * ```
3391
3485
  *
3392
- * @group Templates
3393
- * @api POST /v2/templates/from-sharepoint Create a template from an asset in Sharepoint
3394
- * @apiBody string name Name for the new template
3395
- * @apiBody string siteId Name for the new template
3396
- * @apiBody string itemId Name for the new template
3397
- * @apiBody string oboToken On-Behalf-Of token for calls to Sharepoint. Should be generated as a short-expiration token with at least Read privileges to the siteId/itemId. This token will be discarded after being used.
3398
- * @apiSuccess ITemplate . The newly-created template
3486
+ * @group Organizations
3487
+ * @api POST /v2/organizations Create organization
3488
+ * @apiDescription The caller will be assigned an "Owner" profile in the new organization, and it will be set to "current" automatically. A new set of session tokens will be issued to the caller, and the caller should update their endpoint to use the new tokens.
3489
+ * @apiBody string name The name of the new organization
3490
+ * @apiBody string parent_id? If set, the new organization will be created as a child of the specified parent organization. The caller must be an admin of the parent organization.
3491
+ * @apiBody string contact_email? Contact email for the new organization
3492
+ * @apiBody string url? URL for the new organization
3493
+ * @apiBody string full_logo_url? URL of a large-format PNG logo
3494
+ * @apiBody string thumbnail_url? URL of a small-format (square is recommended) PNG logo
3495
+ * @apiBody string primary_color? URL of a small-format (square is recommended) PNG logo
3496
+ * @apiBody string secondary_color? URL of a small-format (square is recommended) PNG logo
3497
+ * @apiSuccess IAuthenticateResponse . Authentication credentials for user in the new organization. The user will be made an Owner automatically.
3399
3498
  */
3400
- const createTemplateFromSharepoint = (endpoint, params) => {
3401
- const options = {
3402
- timeout: 120000,
3403
- };
3404
- return endpoint.api.post('/v2/templates/from-sharepoint', params, options).then((r) => r.data);
3405
- };
3499
+ const createOrganization = (endpoint, params) => endpoint.api //
3500
+ .post(`/v2/organizations`, params)
3501
+ .then((r) => r.data);
3406
3502
  /**
3407
- * Update a template.
3503
+ * Update an organization. This can only be called by an admin or owner.
3408
3504
  *
3409
3505
  * ```typescript
3410
- * import {updateTemplate} from '@verdocs/js-sdk/Templates';
3506
+ * import {updateOrganization} from '@verdocs/js-sdk';
3411
3507
  *
3412
- * const updatedTemplate = await updateTemplate((VerdocsEndpoint.getDefault(), '83da3d70-7857-4392-b876-c4592a304bc9', { name: 'New Name' });
3508
+ * const organizations = await updateOrganization(VerdocsEndpoint.getDefault(), organizationId, {name:'ORGNAME'});
3413
3509
  * ```
3414
3510
  *
3415
- * @group Templates
3416
- * @api PATCH /v2/templates/:template_id Update a template
3417
- * @apiBody string name? Template name
3418
- * @apiBody string description? Optional description
3419
- * @apiBody TTemplateVisibility visibility? Visibility setting
3420
- * @apiBody boolean is_personal? Deprecated. If true, the template is personal and can only be seen by the caller. (Use "visibility" for new calls.)
3421
- * @apiBody boolean is_public? Deprecated. If true, the template is public and can be seen by anybody. (Use "visibility" for new calls.)
3422
- * @apiBody TTemplateSender sender? Who may send envelopes using this template
3423
- * @apiBody number initial_reminder? Delay (in seconds) before the first reminder is sent (min: 4hrs). Set to 0 or null to disable.
3424
- * @apiBody number followup_reminders? Delay (in seconds) before the subsequent reminders are sent (min: 12hrs). Set to 0 or null to disable.
3425
- * @apiSuccess ITemplate . The updated template
3511
+ * @group Organizations
3512
+ * @api PATCH /v2/organizations/:organization_id Update organization
3513
+ * @apiBody string name The name of the new organization
3514
+ * @apiBody string contact_email? Contact email for the new organization
3515
+ * @apiBody string url? URL for the new organization
3516
+ * @apiBody string full_logo_url? URL of a large-format PNG logo
3517
+ * @apiBody string thumbnail_url? URL of a small-format (square is recommended) PNG logo
3518
+ * @apiBody string primary_color? URL of a small-format (square is recommended) PNG logo
3519
+ * @apiBody string secondary_color? URL of a small-format (square is recommended) PNG logo
3520
+ * @apiSuccess IOrganization . The details for the updated organization
3426
3521
  */
3427
- const updateTemplate = (endpoint, templateId, params) => endpoint.api //
3428
- .patch(`/v2/templates/${templateId}`, params)
3522
+ const updateOrganization = (endpoint, organizationId, params) => endpoint.api //
3523
+ .patch(`/v2/organizations/${organizationId}`, params)
3429
3524
  .then((r) => r.data);
3430
3525
  /**
3431
- * Delete a template.
3526
+ * Delete an organization. This can only be called by an owner. Inclusion of the organization ID to delete
3527
+ * is just a safety check. The caller may only delete the organization they have currently selected.
3432
3528
  *
3433
3529
  * ```typescript
3434
- * import {deleteTemplate} from '@verdocs/js-sdk/Templates';
3530
+ * import {deleteOrganization} from '@verdocs/js-sdk';
3435
3531
  *
3436
- * await deleteTemplate((VerdocsEndpoint.getDefault(), '83da3d70-7857-4392-b876-c4592a304bc9');
3532
+ * const newSession = await deleteOrganization(VerdocsEndpoint.getDefault(), organizationId);
3437
3533
  * ```
3438
3534
  *
3439
- * @group Templates
3440
- * @api DELETE /v2/templates/:template_id Delete a template
3441
- * @apiSuccess string . Success
3535
+ * @group Organizations
3536
+ * @api DELETE /v2/organizations/:organization_id Delete organization
3537
+ * @apiSuccess IAuthenticateResponse . If the caller is a member of another organization, authentication credentials for the next organization available. If not, this will be null and the caller will be logged out.
3442
3538
  */
3443
- const deleteTemplate = (endpoint, templateId) => endpoint.api //
3444
- .delete(`/v2/templates/${templateId}`)
3539
+ const deleteOrganization = (endpoint, organizationId) => endpoint.api //
3540
+ .delete(`/v2/organizations/${organizationId}`)
3445
3541
  .then((r) => r.data);
3446
3542
  /**
3447
- * Toggle the template star for a template.
3543
+ * Update the organization's full or thumbnail logo. This can only be called by an admin or owner.
3448
3544
  *
3449
3545
  * ```typescript
3450
- * import {toggleTemplateStar} from '@verdocs/js-sdk/Templates';
3546
+ * import {updateOrganizationLogo} from '@verdocs/js-sdk';
3451
3547
  *
3452
- * await toggleTemplateStar((VerdocsEndpoint.getDefault(), '83da3d70-7857-4392-b876-c4592a304bc9');
3548
+ * await updateOrganizationLogo((VerdocsEndpoint.getDefault(), organizationId, file);
3453
3549
  * ```
3454
3550
  *
3455
- * @group Templates
3456
- * @api POST /v2/templates/:template_id/star Star or unstar a template (toggle state)
3457
- * @apiSuccess ITemplate . Success
3458
- */
3459
- const toggleTemplateStar = (endpoint, templateId) => endpoint.api //
3460
- .post(`/v2/templates/${templateId}/stars/toggle`)
3461
- .then((r) => r.data);
3462
-
3463
- /**
3464
- * A TemplateDocument represents a PDF or other attachment in a Template.
3465
- *
3466
- * @module
3551
+ * @group Organizations
3552
+ * @api PATCH /v2/organizations/:organization_id Update organization full or thumbnail logo.
3553
+ * @apiBody image/png logo? Form-url-encoded file to upload
3554
+ * @apiBody image/png thumbnail? Form-url-encoded file to upload
3555
+ * @apiSuccess IOrganization . The updated organization.
3467
3556
  */
3557
+ const updateOrganizationLogo = (endpoint, organizationId, file, onUploadProgress) => {
3558
+ const formData = new FormData();
3559
+ formData.append('logo', file, file.name);
3560
+ return endpoint.api //
3561
+ .patch(`/v2/organizations/${organizationId}`, formData, {
3562
+ timeout: 120000,
3563
+ onUploadProgress: (event) => {
3564
+ const total = event.total || 1;
3565
+ const loaded = event.loaded || 0;
3566
+ onUploadProgress?.(Math.floor((loaded * 100) / (total)), loaded, total);
3567
+ },
3568
+ })
3569
+ .then((r) => r.data);
3570
+ };
3468
3571
  /**
3469
- * Create a Document for a particular Template.
3572
+ * Update the organization's thumbnail. This can only be called by an admin or owner.
3470
3573
  *
3471
3574
  * ```typescript
3472
- * import {TemplateDocument} from '@verdocs/js-sdk/Templates';
3575
+ * import {updateOrganizationThumbnail} from '@verdocs/js-sdk';
3473
3576
  *
3474
- * await TemplateDocument.createDocument((VerdocsEndpoint.getDefault(), templateID, params);
3577
+ * await updateOrganizationThumbnail((VerdocsEndpoint.getDefault(), organizationId, file);
3475
3578
  * ```
3476
- *
3477
- * @group Template Documents
3478
- * @api POST /v2/templates/:template_id/documents Attach a document to a template
3479
- * @apiBody string(format:binary) file Document file to attach. The file name will automatically be used as the document name.
3480
- * @apiSuccess ITemplateDocument . Template document
3481
3579
  */
3482
- const createTemplateDocument = (endpoint, template_id, file, onUploadProgress) => {
3580
+ const updateOrganizationThumbnail = (endpoint, organizationId, file, onUploadProgress) => {
3483
3581
  const formData = new FormData();
3484
- formData.append('document', file, file.name);
3485
- formData.append('template_id', template_id);
3582
+ formData.append('thumbnail', file, file.name);
3486
3583
  return endpoint.api //
3487
- .post(`/v2/template-documents`, formData, {
3584
+ .patch(`/v2/organizations/${organizationId}`, formData, {
3488
3585
  timeout: 120000,
3489
3586
  onUploadProgress: (event) => {
3490
3587
  const total = event.total || 1;
@@ -3494,119 +3591,86 @@ const createTemplateDocument = (endpoint, template_id, file, onUploadProgress) =
3494
3591
  })
3495
3592
  .then((r) => r.data);
3496
3593
  };
3594
+ const getEntitlements = async (endpoint) => endpoint.api.get(`/v2/organizations/entitlements`).then((r) => r.data);
3497
3595
  /**
3498
- * Delete a specific Document.
3596
+ * Largely intended to be used internally by Web SDK components but may be informative for other cases.
3597
+ * Entitlements are feature grants such as "ID-based KBA" that require paid contracts to enable, typically
3598
+ * because the underlying services that support them are fee-based. Entitlements may run concurrently,
3599
+ * and may have different start/end dates e.g. "ID-based KBA" may run 1/1/2026-12/31/2026 while
3600
+ * "SMS Authentication" may be added later and run 6/1/2026-5/31/2027. The entitlements list is a simple
3601
+ * array of enablements and may include entries that are not YET enabled or have now expired.
3602
+ *
3603
+ * In client code it is helpful to simply know "is XYZ feature currently enabled?" This function collapses
3604
+ * the entitlements list to a simplified dictionary of current/active entitlements. Note that it is async
3605
+ * because it calls the server to obtain the "most current" entitlements list. Existence of an entry in the
3606
+ * resulting dictionary implies the feature is active. Metadata inside each entry can be used to determine
3607
+ * limits, etc.
3499
3608
  *
3500
3609
  * ```typescript
3501
- * import {TemplateDocument} from '@verdocs/js-sdk/Templates';
3610
+ * import {getActiveEntitlements} from '@verdocs/js-sdk';
3502
3611
  *
3503
- * await TemplateDocument.deleteDocument((VerdocsEndpoint.getDefault(), templateID, documentID);
3612
+ * const activeEntitlements = await getActiveEntitlements((VerdocsEndpoint.getDefault());
3613
+ * const isSMSEnabled = !!activeEntitlements.sms_auth;
3614
+ * const monthlyKBALimit = activeEntitlements.kba_auth?.monthly_max;
3504
3615
  * ```
3505
- *
3506
- * @group Template Documents
3507
- * @api DELETE /v2/templates/:temlate_id/documents/:document_id Delete a template document
3508
- * @apiSuccess string . Success
3509
3616
  */
3510
- const deleteTemplateDocument = (endpoint, templateId, documentId) => endpoint.api //
3511
- .delete(`/v2/templates/${templateId}/documents/${documentId}`)
3512
- .then((r) => r.data);
3617
+ const getActiveEntitlements = async (endpoint) => {
3618
+ if (!endpoint.session) {
3619
+ throw new Error('No active session');
3620
+ }
3621
+ const entitlements = await getEntitlements(endpoint);
3622
+ return collapseEntitlements(entitlements);
3623
+ };
3624
+
3513
3625
  /**
3514
- * Get all metadata for a template document. Note that when called by non-creators (e.g. Org Collaborators)
3515
- * this will return only the **metadata** the caller is allowed to view.
3626
+ * Webhooks are callback triggers from Verdocs to your servers that notify your applications
3627
+ * of various events, such as signing operations.
3516
3628
  *
3517
- * @group Template Documents
3518
- * @api GET /v2/envelope-documents/:id Get envelope document
3519
- * @apiParam string(format: 'uuid') document_id The ID of the document to retrieve.
3520
- * @apiSuccess IEnvelopeDocument . The detailed metadata for the document requested
3521
- */
3522
- const getTemplateDocument = async (endpoint, documentId) => endpoint.api //
3523
- .get(`/v2/template-documents/${documentId}`)
3524
- .then((r) => r.data);
3525
- /**
3526
- * Download a document directly.
3629
+ * @module
3527
3630
  */
3528
- const downloadTemplateDocument = async (endpoint, documentId) => endpoint.api //
3529
- .get(`/v2/template-documents/${documentId}?type=file`, { responseType: 'blob' })
3530
- .then((r) => r.data);
3531
3631
  /**
3532
- * Get an envelope document's metadata, or the document itself. If no "type" parameter is specified,
3533
- * the document metadata is returned. If "type" is set to "file", the document binary content is
3534
- * returned with Content-Type set to the MIME type of the file. If "type" is set to "download", a
3535
- * string download link will be returned. If "type" is set to "preview" a string preview link will
3536
- * be returned. This link expires quickly, so it should be accessed immediately and never shared.
3632
+ * Get the registered Webhook configuration for the caller's organization.
3537
3633
  *
3538
- * @group Template Documents
3539
- * @api GET /v2/envelope-documents/:document_id Preview, Download, or Link to a Document
3540
- * @apiParam string(format: 'uuid') document_id The ID of the document to retrieve.
3541
- * @apiQuery string(enum:'file'|'download'|'preview') type? Download the file directly, generate a download link, or generate a preview link.
3542
- * @apiSuccess string . The generated link.
3543
- */
3544
- const getTemplateDocumentDownloadLink = async (endpoint, _envelopeId, documentId) => endpoint.api //
3545
- .get(`/v2/template-documents/${documentId}?type=download`)
3546
- .then((r) => r.data);
3547
- /**
3548
- * Get a pre-signed preview link for an Envelope Document. This link expires quickly, so it should
3549
- * be accessed immediately and never shared. Content-Disposition will be set to "inline".
3550
- */
3551
- const getTemplateDocumentPreviewLink = async (endpoint, _envelopeId, documentId) => endpoint.api //
3552
- .get(`/v2/envelope-documents/${documentId}?type=preview`)
3553
- .then((r) => r.data);
3554
- /**
3555
- * Get (binary download) a file attached to a Template. It is important to use this method
3556
- * rather than a direct A HREF or similar link to set the authorization headers for the
3557
- * request.
3634
+ * ```typescript
3635
+ * import {getWebhooks} from '@verdocs/js-sdk';
3636
+ *
3637
+ * await getWebhooks(ORGID, params);
3638
+ * ```
3639
+ *
3640
+ * @group Webhooks
3641
+ * @api GET /v2/webhooks Get organization Webhooks config
3642
+ * @apiSuccess IWebhook . The current Webhooks config for the caller's organization.
3558
3643
  */
3559
- const getTemplateDocumentFile = async (endpoint, templateId, documentId) => endpoint.api //
3560
- .get(`/v2/templates/${templateId}/documents/${documentId}?file=true`, { responseType: 'blob' })
3644
+ const getWebhooks = (endpoint) => endpoint.api //
3645
+ .get(`/v2/webhooks`)
3561
3646
  .then((r) => r.data);
3562
3647
  /**
3563
- * Get (binary download) a file attached to a Template. It is important to use this method
3564
- * rather than a direct A HREF or similar link to set the authorization headers for the
3565
- * request.
3648
+ * Update the registered Webhook configuration for the caller's organization. Note that
3649
+ * Webhooks cannot currently be deleted, but may be easily disabled by setting `active`
3650
+ * to `false` and/or setting the `url` to an empty string.
3651
+ *
3652
+ * ```typescript
3653
+ * import {setWebhooks} from '@verdocs/js-sdk';
3654
+ *
3655
+ * await setWebhooks(ORGID, params);
3656
+ * ```
3657
+ *
3658
+ * @group Webhooks
3659
+ * @api PATCH /v2/webhooks Update organization Webhooks config
3660
+ * @apiDescription Note that Webhooks cannot currently be deleted, but may be easily disabled by setting `active` to `false` and/or setting the `url` to an empty string.
3661
+ * @apiBody string url URL to send Webhook events to. An empty or invalid URL will disable Webhook calls.
3662
+ * @apiBody boolean active Set to true to enable Webhooks calls.
3663
+ * @apiBody object events Record<TWebhookEvent, boolean> map of events to enable/disable.
3664
+ * @apiSuccess IWebhook . The updated Webhooks config for the caller's organization.
3566
3665
  */
3567
- const getTemplateDocumentThumbnail = async (endpoint, templateId, documentId) => endpoint.api //
3568
- .get(`/v2/templates/${templateId}/documents/${documentId}?thumbnail=true`, { responseType: 'blob' })
3666
+ const setWebhooks = (endpoint, params) => endpoint.api //
3667
+ .patch(`/v2/webhooks`, params)
3569
3668
  .then((r) => r.data);
3570
- /**
3571
- * Get a display URI for a given page in a file attached to a template document. These pages are rendered server-side
3572
- * into PNG resources suitable for display in IMG tags although they may be used elsewhere. Note that these are intended
3573
- * for DISPLAY ONLY, are not legally binding documents, and do not contain any encoded metadata from participants. The
3574
- * original asset may be obtained by calling `getTemplateDocumentFile()` or similar.
3575
- */
3576
- const getTemplateDocumentPageDisplayUri = async (endpoint, documentId, page, variant = 'original') => endpoint.api.get(`/v2/template-documents/page-image/${documentId}/${variant}/${page}`, { timeout: 20000 }).then((r) => r.data);
3577
-
3578
- const EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
3579
- // @see https://www.regextester.com/1978
3580
- const PHONE_REGEX = /((?:\+|00)[17](?: |\-)?|(?:\+|00)[1-9]\d{0,2}(?: |\-)?|(?:\+|00)1\-\d{3}(?: |\-)?)?(0\d|\([0-9]{3}\)|[1-9]{0,3})(?:((?: |\-)[0-9]{2}){4}|((?:[0-9]{2}){4})|((?: |\-)[0-9]{3}(?: |\-)[0-9]{4})|([0-9]{7}))/;
3581
- const URL_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
3582
- const POSTAL_CODE_REGEX = /^[A-Za-z0-9-\s]{3,10}$/;
3583
- const NUMBER_REGEX = /^\d+$/;
3584
- const DATE_REGEX = /^(\d{4}[-\/]\d{2}[-\/]\d{2})|(\d{2}[-\/]\d{2}[-\/]\d{4})$/;
3585
- const VALIDATORS = {
3586
- email: { regex: EMAIL_REGEX, label: 'Email Address' },
3587
- phone: { regex: PHONE_REGEX, label: 'Phone Number' },
3588
- url: { regex: URL_REGEX, label: 'URL' },
3589
- postal_code: { regex: POSTAL_CODE_REGEX, label: 'Zip/Postal Code' },
3590
- number: { regex: NUMBER_REGEX, label: 'Number' },
3591
- date: { regex: DATE_REGEX, label: 'Date' },
3592
- };
3593
- const isValidInput = (value, validator) => Object.keys(VALIDATORS).includes(validator) && VALIDATORS[validator].regex.test(value);
3594
- /**
3595
- * Get a list of available validators for field inputs. Note that validators always check strings,
3596
- * because that is all a user can enter in an HTML input field. Numeric-format validators should
3597
- * perform any necessary conversions internally. Validators never throw - they just return a boolean.
3598
- * indicating whether the value is valid.
3599
- */
3600
- const getValidators = () => Object.keys(VALIDATORS);
3601
- const isValidEmail = (email) => !!email && EMAIL_REGEX.test(email);
3602
- const isValidPhone = (phone) => !!phone && PHONE_REGEX.test(phone);
3603
- const isValidRoleName = (value, roles) => roles.findIndex((role) => role.name === value) !== -1;
3604
- const TagRegEx = /^[a-zA-Z0-9-]{0,32}$/;
3605
- const isValidTag = (value, tags) => TagRegEx.test(value) || tags.findIndex((tag) => tag === value) !== -1;
3606
3669
 
3607
3670
  exports.ALL_PERMISSIONS = ALL_PERMISSIONS;
3608
3671
  exports.AtoB = AtoB;
3609
3672
  exports.Countries = Countries;
3673
+ exports.DEFAULT_DISCLOSURES = DEFAULT_DISCLOSURES;
3610
3674
  exports.DEFAULT_FIELD_HEIGHTS = DEFAULT_FIELD_HEIGHTS;
3611
3675
  exports.DEFAULT_FIELD_WIDTHS = DEFAULT_FIELD_WIDTHS;
3612
3676
  exports.FIELD_TYPES = FIELD_TYPES;
@@ -3724,6 +3788,8 @@ exports.isCanada = isCanada;
3724
3788
  exports.isDominicanRepublic = isDominicanRepublic;
3725
3789
  exports.isEnvelopeOwner = isEnvelopeOwner;
3726
3790
  exports.isEnvelopeRecipient = isEnvelopeRecipient;
3791
+ exports.isFieldFilled = isFieldFilled;
3792
+ exports.isFieldValid = isFieldValid;
3727
3793
  exports.isFrenchGuiana = isFrenchGuiana;
3728
3794
  exports.isGuadeloupe = isGuadeloupe;
3729
3795
  exports.isMartinique = isMartinique;