├── .gitignore ├── images ├── image1.png ├── image2.png ├── image3.png ├── image4.png ├── image5.png ├── image6.png ├── image7.png ├── image8.png ├── image9.png └── ConnectConfigSIM.png ├── demo_agreements ├── .DS_Store ├── EmployeeNDA_BO-8.docx ├── MSA_BO-8_Keranos.docx ├── NDA_BO-8_Fontara_v1.docx ├── SOW_BO-8_Fontara_2.docx ├── SOW_BO-8_Fontara_Mktg.docx ├── MSA_BO-8_Innovate Global.docx ├── MSA_BO-8_Momentum Driver.docx ├── MSA_BO-8_Nesis Biotech.docx ├── MSA_BO-8_Pathway Placers.docx ├── MSA_BO-8_Stellar Logical.docx ├── MSA_BO-8_Fontara_Mktg_OLD.docx ├── MSA_BO-8_Insight Baybridge.docx ├── SOW_BO-8_Insight Baybridge.docx ├── MSA_BO-8_CloudMatrix Dynamics.docx ├── MSA_BO-8_DataVault Dynamics.docx ├── MSA_BO-8_EvoLink Networks LLC.docx ├── MSA_BO-8_MarketPulse Dynamics.docx ├── MSA_BO-8_SmartFactory Systems.docx ├── MSA_BO-8_TechVerse Solutions.docx ├── SOW_BO-8_MarketPulse Dynamics.docx ├── SOW_BO-8_SmartFactory Systems.docx ├── MSA_BO-8_Helix Learning _.docx ├── MSA_BO-8_ScoutVision Recruiting.docx ├── MSA_BO-8_SmartPulse Enterprises.docx ├── MSA_BO-8_Virtual Smart Products.docx ├── MSA_BO-8_HyperGrid Design Agency.docx ├── MSA_BO-8_Virtual Tech Collective.docx ├── SOW_BO-8_Virtual Tech Collective.docx ├── CERTIFICATE OF INSURANCE_BO-8_Helix.docx ├── License_BO-8_Virtual Tech Collective.docx ├── CERTIFICATE OF INSURANCE_BO-8_Pathway.docx ├── MSA_BO-8_Dynamic Skillz Strategies Inc.docx └── MSA_BO-8_Hidden Continent Environmental.docx ├── src ├── deleteAgreement.js ├── client.js ├── getAgreements.js ├── auth.js └── bulkUploadAgreements.js ├── example.env ├── package.json ├── bulkUploadWithAPI.md ├── createWebhooks.md ├── oauthWithAIAssistant.md ├── server.js ├── public └── index.html └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Environment variables 5 | .env -------------------------------------------------------------------------------- /images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/images/image1.png -------------------------------------------------------------------------------- /images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/images/image2.png -------------------------------------------------------------------------------- /images/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/images/image3.png -------------------------------------------------------------------------------- /images/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/images/image4.png -------------------------------------------------------------------------------- /images/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/images/image5.png -------------------------------------------------------------------------------- /images/image6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/images/image6.png -------------------------------------------------------------------------------- /images/image7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/images/image7.png -------------------------------------------------------------------------------- /images/image8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/images/image8.png -------------------------------------------------------------------------------- /images/image9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/images/image9.png -------------------------------------------------------------------------------- /demo_agreements/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/.DS_Store -------------------------------------------------------------------------------- /images/ConnectConfigSIM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/images/ConnectConfigSIM.png -------------------------------------------------------------------------------- /demo_agreements/EmployeeNDA_BO-8.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/EmployeeNDA_BO-8.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_Keranos.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_Keranos.docx -------------------------------------------------------------------------------- /demo_agreements/NDA_BO-8_Fontara_v1.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/NDA_BO-8_Fontara_v1.docx -------------------------------------------------------------------------------- /demo_agreements/SOW_BO-8_Fontara_2.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/SOW_BO-8_Fontara_2.docx -------------------------------------------------------------------------------- /demo_agreements/SOW_BO-8_Fontara_Mktg.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/SOW_BO-8_Fontara_Mktg.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_Innovate Global.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_Innovate Global.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_Momentum Driver.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_Momentum Driver.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_Nesis Biotech.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_Nesis Biotech.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_Pathway Placers.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_Pathway Placers.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_Stellar Logical.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_Stellar Logical.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_Fontara_Mktg_OLD.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_Fontara_Mktg_OLD.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_Insight Baybridge.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_Insight Baybridge.docx -------------------------------------------------------------------------------- /demo_agreements/SOW_BO-8_Insight Baybridge.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/SOW_BO-8_Insight Baybridge.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_CloudMatrix Dynamics.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_CloudMatrix Dynamics.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_DataVault Dynamics.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_DataVault Dynamics.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_EvoLink Networks LLC.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_EvoLink Networks LLC.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_MarketPulse Dynamics.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_MarketPulse Dynamics.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_SmartFactory Systems.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_SmartFactory Systems.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_TechVerse Solutions.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_TechVerse Solutions.docx -------------------------------------------------------------------------------- /demo_agreements/SOW_BO-8_MarketPulse Dynamics.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/SOW_BO-8_MarketPulse Dynamics.docx -------------------------------------------------------------------------------- /demo_agreements/SOW_BO-8_SmartFactory Systems.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/SOW_BO-8_SmartFactory Systems.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_Helix Learning _.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_Helix Learning _.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_ScoutVision Recruiting.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_ScoutVision Recruiting.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_SmartPulse Enterprises.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_SmartPulse Enterprises.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_Virtual Smart Products.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_Virtual Smart Products.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_HyperGrid Design Agency.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_HyperGrid Design Agency.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_Virtual Tech Collective.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_Virtual Tech Collective.docx -------------------------------------------------------------------------------- /demo_agreements/SOW_BO-8_Virtual Tech Collective.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/SOW_BO-8_Virtual Tech Collective.docx -------------------------------------------------------------------------------- /demo_agreements/CERTIFICATE OF INSURANCE_BO-8_Helix.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/CERTIFICATE OF INSURANCE_BO-8_Helix.docx -------------------------------------------------------------------------------- /demo_agreements/License_BO-8_Virtual Tech Collective.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/License_BO-8_Virtual Tech Collective.docx -------------------------------------------------------------------------------- /demo_agreements/CERTIFICATE OF INSURANCE_BO-8_Pathway.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/CERTIFICATE OF INSURANCE_BO-8_Pathway.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_Dynamic Skillz Strategies Inc.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_Dynamic Skillz Strategies Inc.docx -------------------------------------------------------------------------------- /demo_agreements/MSA_BO-8_Hidden Continent Environmental.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/docusign-discover-workshop-2/main/demo_agreements/MSA_BO-8_Hidden Continent Environmental.docx -------------------------------------------------------------------------------- /src/deleteAgreement.js: -------------------------------------------------------------------------------- 1 | // Deletes a single agreement by its ID. 2 | import { makeClient } from './client.js'; 3 | 4 | export async function deleteAgreement({ agreementId, force = false, accessToken } = {}) { 5 | // TODO: Add sdk call to delete an agreement. 6 | return { ok: true, deletedId: agreementId }; 7 | } 8 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | DS_CLIENT_ID={DS_CLIENT_ID} 2 | DS_SECRET_KEY={DS_SECRET_KEY} 3 | DS_ACCESS_TOKEN={DS_ACCESS_TOKEN} 4 | DS_REDIRECT_URI=http://localhost:3000/ds/callback 5 | DS_ACCOUNT_ID=12345678-1234-1234-1234-123456788912 #{DS_ACCOUNT_ID} 6 | BASE_URL=https://ff8f3e28-f5fd-473e-afc3-1ae502d0f0c5.mock.pstmn.io #https://api-d.docusign.com 7 | DS_ENV=development -------------------------------------------------------------------------------- /src/client.js: -------------------------------------------------------------------------------- 1 | import { IamClient } from '@docusign/iam-sdk'; 2 | 3 | export function makeClient(accessToken) { 4 | if (!accessToken) throw new Error('Access token required') 5 | 6 | return new IamClient({ 7 | basePath: process.env.DS_ENV === 'prod' 8 | ? 'https://api.docusign.com' 9 | : 'https://api-d.docusign.com', 10 | accessToken 11 | }) 12 | } 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "navigator-mini-dashboard", 3 | "version": "1.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "start": "node server.js", 8 | "dev": "nodemon server.js" 9 | }, 10 | "dependencies": { 11 | "@docusign/iam-sdk": "^1.0.0-beta.3", 12 | "cookie-parser": "^1.4.7", 13 | "dotenv": "^16.4.5", 14 | "express": "^4.19.2", 15 | "express-session": "^1.18.2", 16 | "morgan": "^1.10.0" 17 | }, 18 | "devDependencies": { 19 | "nodemon": "^3.1.10" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /bulkUploadWithAPI.md: -------------------------------------------------------------------------------- 1 | # Bulk Upload using the Navigator API 2 | 3 | ## Bulk Upload Steps 4 | 5 | Bulk upload consists of 3 steps: 6 | 7 | **1. Create the job** 8 | 9 | POST to `https://{{host}}/v1/accounts/{{accountId}}/upload/jobs`, passing a JSON body: 10 | 11 | ```json 12 | { 13 | "job_name":"test_name", 14 | "expected_number_of_docs":1, 15 | "language":"en_us" 16 | } 17 | ``` 18 | 19 | A successful response includes one or more unique upload URLs corresponding to the `expected_number_of_docs` value in the request. 20 | 21 | **2. Upload documents** 22 | 23 | PUT to each unique URL, passing a document in the body as binary data. 24 | 25 | A successful response is a 201 with no data. 26 | 27 | **3. Complete the upload** 28 | 29 | POST to `https://{{host}}/v1/accounts/{{accountId}}/upload/jobs/{{jobId_int}}/actions/complete`. 30 | 31 | A successful response provides data about the job, including the status. 32 | 33 | ## Check Bulk Upload Status 34 | 35 | GET `https://{{host}}/v1/accounts/{{accountId}}/upload/jobs/{{jobId_int}}`. 36 | 37 | A successful response provides data about the job, including the status. -------------------------------------------------------------------------------- /createWebhooks.md: -------------------------------------------------------------------------------- 1 | # Create Webhooks for Navigator Events 2 | 3 | ## Navigator Webhook Events 4 | 5 | | Event | Trigger | 6 | |----------------------------------|-----------------------------------------------------| 7 | | `agreement-created` | a new agreement is created in Navigator | 8 | | `agreement-extractions-reviewed` | the number of pending extraction reviews is reduced | 9 | | `agreement-reviews-complete` | the number of pending extraction reviews reaches 0 | 10 | | `agreement-updated` | a user manually modifies an agreement in Navigator | 11 | | `agreement-deleted` | an agreement is deleted in Navigator | 12 | 13 | ## Creating Custom Configurations Using Navigator Events 14 | 15 | These events are available only for custom Connect configurations created using the **JSON SIM** event model message format. 16 | 17 | To create a JSON SIM configuration, use the default values for **data format** (REST v2.1) and **event message delivery mode** (SIM) as shown in the following image: 18 | 19 | ![Connect Config SIM](./images/ConnectConfigSIM.png) 20 | 21 | -------------------------------------------------------------------------------- /src/getAgreements.js: -------------------------------------------------------------------------------- 1 | // Lists agreements using the Docusign IAM Navigator SDK. 2 | import { makeClient } from './client.js'; 3 | 4 | // Mock data 5 | // Lists agreements using the Docusign IAM Navigator API directly 6 | export async function getAgreements({ accessToken } = {}) { 7 | // if (!accessToken) throw new Error('Access token required'); 8 | const accountId = process.env.DS_ACCOUNT_ID; 9 | // if (!accountId) throw new Error('ACCOUNT_ID missing'); 10 | 11 | const baseUrl = process.env.BASE_URL; 12 | 13 | try { 14 | const res = await fetch(`${baseUrl}/v1/accounts/${accountId}/agreements`, { 15 | method: 'GET', 16 | headers: { 17 | 'Authorization': `Bearer ${accessToken}`, 18 | 'Accept': 'application/json', 19 | 'Content-Type': 'application/json' 20 | } 21 | }); 22 | 23 | if (!res.ok) { 24 | const text = await res.text(); 25 | let error; 26 | try { 27 | const json = JSON.parse(text); 28 | error = json.message || json.error || text; 29 | } catch (e) { 30 | error = text; 31 | } 32 | throw new Error(`Agreements API failed: ${res.status} ${error}`); 33 | } 34 | 35 | const data = await res.json(); 36 | 37 | const items = (data?.items ?? data?.data ?? []); 38 | console.log(`Agreements fetched: ${JSON.stringify(items, null, 2)}`); 39 | 40 | // Normalize response shape 41 | return { 42 | items: items.map(a => ({ 43 | agreementId: a.agreementId ?? a.id ?? '', 44 | name: a.name ?? a.title ?? '', 45 | status: a.review_status ?? 'unknown', 46 | category: a.category ?? 'unknown', 47 | raw: a 48 | })) 49 | }; 50 | } catch (err) { 51 | console.error('getAgreements failed:', err); 52 | throw err; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/auth.js: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { AuthUtils, IamClient } from '@docusign/iam-sdk'; 3 | 4 | const CLIENT_ID = process.env.DS_CLIENT_ID; 5 | const SECRET_KEY = process.env.DS_SECRET_KEY; 6 | const REDIRECT_URI = process.env.DS_REDIRECT_URI; 7 | 8 | if (!CLIENT_ID || !SECRET_KEY) { 9 | console.warn('Missing DS client id / secret. Set DS_CLIENT_ID and DS_SECRET_KEY.'); 10 | } 11 | 12 | function buildAuthUrl({ 13 | redirectUri = REDIRECT_URI, 14 | scopes = [ 15 | "adm_store_unified_repo_read", 16 | "adm_store_unified_repo_write", 17 | "document_uploader_write", 18 | "document_uploader_read", 19 | "aow_manage", 20 | "signature" 21 | ], 22 | state = '' 23 | } = {}) { 24 | if(process.env.DS_ACCESS_TOKEN) { 25 | return "/ds/callback"; 26 | } 27 | 28 | if (!CLIENT_ID) throw new Error('CLIENT_ID missing'); 29 | return AuthUtils.createAuthorizationUrl({ 30 | type: 'code', 31 | clientId: CLIENT_ID, 32 | redirectUri, 33 | scopes, 34 | state, 35 | }); 36 | } 37 | 38 | /** 39 | * Exchange authorization code for tokens using the @docusign/iam-sdk 40 | * returns { accessToken, refreshToken, expiresIn, raw } 41 | */ 42 | async function exchangeCodeForToken(code, { redirectUri = REDIRECT_URI } = {}) { 43 | if (!code) throw new Error('authorization code is required'); 44 | if (!CLIENT_ID || !SECRET_KEY) throw new Error('CLIENT_ID or SECRET_KEY missing'); 45 | 46 | const iam = new IamClient(); 47 | try { 48 | // TODO: Implement token exchange via sdk 49 | 50 | return {}; 51 | } catch (err) { 52 | const msg = err?.errorDescription ?? err?.error_description ?? err?.message ?? String(err); 53 | const e = new Error(`Token exchange failed: ${msg}`); 54 | e.cause = err; 55 | throw e; 56 | } 57 | } 58 | 59 | /** 60 | * Optional: get userinfo via the IAM client 61 | */ 62 | async function getUserInfo(accessToken) { 63 | if (!accessToken) throw new Error('accessToken is required'); 64 | const iam = new IamClient(); 65 | try { 66 | const info = await iam.auth.getUserInfo({ accessToken }); 67 | return info; 68 | } catch (err) { 69 | const msg = err?.message ?? String(err); 70 | const e = new Error(`getUserInfo failed: ${msg}`); 71 | e.cause = err; 72 | throw e; 73 | } 74 | } 75 | 76 | export { 77 | buildAuthUrl, 78 | exchangeCodeForToken, 79 | getUserInfo, 80 | }; -------------------------------------------------------------------------------- /oauthWithAIAssistant.md: -------------------------------------------------------------------------------- 1 | # Log into your Docusign account with Navigator access 2 | 3 | Go to [apps-d.docusign.com](http://apps-d.docusign.com) and sign in with your developer account with Navigator access. 4 | Go to the Agreements tab and click completed to confirm that you have access. You should see a tag that says AI assisted. 5 | 6 | # Obtain an access token Using the Docusign Developer AI Assistant for VS Code 7 | 8 | 1. [Set up GitHub Copilot in VS Code](https://code.visualstudio.com/docs/copilot/setup) 9 | 2. Install the Docusign Developer AI Assistance for VS Code extension in VS Code extensions marketplace: 10 | 11 | 3. Open GitHub Copilot by clicking on the Copilot icon in the Activity Bar or using the shortcut (Ctrl+Alt+P or Cmd+Alt+P on macOS). 12 | 4. Add the AI assistant as a participant in the chat using the command @docusign and sign in with your Docusign developer account (with Navigator access). 13 | 14 | 5. Copy the example.env file from the root of the project directory and save it to a new file named .env. This is where you will save your authentication information from the following steps. 15 | 6. Enter @docusign /getAccessToken and answer the follow the prompts to create and configure an integration key and generate an access token. 16 | 17 | 18 | 19 | 7. Name your app and set the redirect URI to "https://localhost:3000/ds/callback". 20 | 21 | 22 | 23 | 8. Open the consent url and add the following scopes to the url: 24 | 25 | ``` 26 | %20adm_store_unified_repo_write%20document_uploader_write%20document_uploader_read 27 | ``` 28 | 29 | 9. Reload the updated url and grant consent to the listed scopes. 30 | 31 | 32 | 33 | 10. After granting consent, copy the redirect URI. 34 | 35 | 36 | 37 | 11. Paste the URI into VSCode. 38 | 39 | 40 | 41 | 42 | 43 | It should also display your user info, and an example curl request using the access token. 44 | 12. Copy the generated integration key, integration secret, and access token to your .env file as values to the respective environment variables DS\_CLIENT\_ID, DS\_SECRET\_KEY, and DS\_ACCESS\_TOKEN. Copy the account id from the displayed user info to the env variable DS\_ACCOUNT\_ID. 45 | 46 | 47 | 48 | # See your new IK on the apps and keys page: 49 | 50 | Visit [https://admindemo.docusign.com/authenticate?goTo=appsAndKeys](https://admindemo.docusign.com/authenticate?goTo=appsAndKeys) 51 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import express from 'express'; 3 | import morgan from 'morgan'; 4 | import session from 'express-session'; 5 | import cookieParser from 'cookie-parser'; 6 | import { getAgreements } from './src/getAgreements.js'; 7 | import { deleteAgreement } from './src/deleteAgreement.js'; 8 | import { bulkUploadAgreements, bulkUploadStatus } from './src/bulkUploadAgreements.js'; 9 | import { buildAuthUrl, exchangeCodeForToken } from './src/auth.js'; 10 | 11 | const app = express(); 12 | 13 | // Add session support before other middleware 14 | app.use(session({ 15 | secret: process.env.SESSION_SECRET || 'dev-secret-key', 16 | resave: false, 17 | saveUninitialized: false, 18 | cookie: { 19 | secure: process.env.NODE_ENV === 'production', 20 | httpOnly: true 21 | } 22 | })); 23 | 24 | app.use(morgan('dev')); 25 | app.use(express.json()); 26 | app.use(express.static('public')); 27 | app.use(cookieParser()); 28 | 29 | // Auth middleware to protect routes 30 | function requireAuth(req, res, next) { 31 | if (!req.session.accessToken) { 32 | if (req.headers.accept?.includes('application/json')) { 33 | return res.status(401).json({ error: 'Login required' }); 34 | } 35 | return res.redirect('/auth/login'); 36 | } 37 | next(); 38 | } 39 | 40 | // Protected API routes 41 | app.get('/api/getAgreements', requireAuth, async (req, res) => { 42 | try { 43 | const data = await getAgreements({ 44 | accessToken: req.session.accessToken 45 | }); 46 | res.json(data); 47 | } catch (e) { 48 | console.error(e); 49 | res.status(500).json({ error: e.message || 'Failed to fetch agreements' }); 50 | } 51 | }); 52 | 53 | app.delete('/api/deleteAgreement/:agreementId', requireAuth, async (req, res) => { 54 | try { 55 | const { agreementId } = req.params; 56 | const force = req.query.force === 'true'; 57 | const result = await deleteAgreement({ 58 | agreementId, 59 | force, 60 | accessToken: req.session.accessToken 61 | }); 62 | res.json(result); 63 | } catch (e) { 64 | const status = e.code === 'SAFE_GUARD' ? 400 : 500; 65 | res.status(status).json({ error: e.message || 'Failed to delete agreement' }); 66 | } 67 | }); 68 | 69 | // Bulk upload agreements 70 | app.post('/api/bulkUploadAgreements', requireAuth, express.json(), async (req, res) => { 71 | try { 72 | const result = await bulkUploadAgreements({ 73 | accessToken: req.session.accessToken 74 | }); 75 | 76 | // Save jobId in session for future status checks 77 | if (result) { 78 | req.session.jobId = result.jobId; 79 | } 80 | 81 | res.json(result); 82 | } catch (e) { 83 | console.error('bulk upload failed', e); 84 | res.status(500).json({ error: 'Bulk upload failed' }); 85 | } 86 | }); 87 | 88 | // Bulk upload status endpoint 89 | app.get('/api/bulkUploadStatus', requireAuth, async (req, res) => { 90 | try { 91 | const jobId = req.session.jobId; 92 | if (!jobId) { 93 | return res.status(400).json({ error: 'No bulk upload job found in session' }); 94 | } 95 | 96 | const status = await bulkUploadStatus({ jobId, accessToken: req.session.accessToken }); 97 | 98 | res.json(status); 99 | } catch (e) { 100 | console.error('bulk upload status check failed', e); 101 | res.status(500).json({ error: 'Status check failed' }); 102 | } 103 | }); 104 | 105 | 106 | // Auth routes 107 | app.get('/auth/login', (req, res) => { 108 | try { 109 | const state = req.query.state ?? ''; 110 | const url = buildAuthUrl({ state }); 111 | res.redirect(url); 112 | } catch (err) { 113 | console.error('auth/login failed', err); 114 | res.status(500).send('Auth configuration error'); 115 | } 116 | }); 117 | 118 | app.get('/ds/callback', async (req, res) => { 119 | if (process.env.DS_ACCESS_TOKEN){ 120 | console.log("using env token"); 121 | req.session.accessToken = process.env.DS_ACCESS_TOKEN; 122 | return res.redirect('/'); 123 | } 124 | const { code, error } = req.query; 125 | if (error) return res.status(400).send(String(error)); 126 | if (!code) return res.status(400).send('Missing code'); 127 | 128 | try { 129 | const token = await exchangeCodeForToken(String(code)); 130 | // Store token in session 131 | req.session.accessToken = token.accessToken; 132 | res.redirect('/'); 133 | } catch (e) { 134 | console.error('auth callback failed', e); 135 | res.status(500).send('Token exchange failed'); 136 | } 137 | }); 138 | 139 | // Add logout route 140 | app.post('/auth/logout', (req, res) => { 141 | req.session.destroy(); 142 | res.redirect('/'); 143 | }); 144 | 145 | const port = process.env.PORT || 3000; 146 | app.listen(port, () => { 147 | console.log(`Navigator mini dashboard at http://localhost:${port}`); 148 | }); 149 | -------------------------------------------------------------------------------- /src/bulkUploadAgreements.js: -------------------------------------------------------------------------------- 1 | // Uploads 2 agreements (in a hard coded folder). 2 | export async function bulkUploadAgreements({ accessToken } = {}) { 3 | 4 | console.log(`Bulk uploading agreements`); 5 | const accountId = process.env.DS_ACCOUNT_ID; 6 | const baseUrl = process.env.BASE_URL; 7 | try { 8 | // Upload files to each blob URL 9 | const fs = await import('fs'); 10 | const path = await import('path'); 11 | 12 | // Automatically read files from demo_agreements folder 13 | const demoAgreementsDir = './demo_agreements'; 14 | 15 | // Function to recursively find all files 16 | const findFiles = (dir) => { 17 | const files = []; 18 | const items = fs.readdirSync(dir); 19 | 20 | for (const item of items) { 21 | const fullPath = path.join(dir, item); 22 | const stat = fs.statSync(fullPath); 23 | 24 | if (stat.isDirectory()) { 25 | // Recursively search subdirectories 26 | files.push(...findFiles(fullPath)); 27 | } else if (path.extname(item).toLowerCase() === '.docx') { 28 | files.push(fullPath); 29 | } 30 | } 31 | 32 | return files; 33 | }; 34 | 35 | const files = findFiles(demoAgreementsDir); 36 | 37 | if (files.length === 0) { 38 | console.warn('No files found in demo_agreements folder'); 39 | return { jobId: jobId, received: 0 }; 40 | } 41 | 42 | console.log(`Found ${files.length} files:`, files); 43 | 44 | // Step 1: Add a value for body: 45 | const body = {}; 46 | 47 | // Step 2: Add the create job endpoint URL as the first fetch argument: 48 | const res = await fetch(``, { 49 | method: 'POST', 50 | headers: { 51 | 'Authorization': `Bearer ${accessToken}`, 52 | 'Accept': 'application/json', 53 | 'Content-Type': 'application/json' 54 | }, 55 | body: JSON.stringify(body) 56 | }); 57 | 58 | if (res.status >= 200 && res.status < 300) { 59 | const data = await res.json(); 60 | let jobId; 61 | let i; 62 | if (data && data.id) { 63 | jobId = data.id; 64 | } 65 | 66 | if ( 67 | data._embedded && 68 | Array.isArray(data._embedded.documents) && 69 | data._embedded.documents.length > 0 70 | ) { 71 | const blobUrls = data._embedded.documents 72 | .filter(doc => doc._actions && doc._actions.upload_document) 73 | .map(doc => doc._actions.upload_document); 74 | 75 | for (i = 0; i < Math.min(blobUrls.length, files.length); i++) { 76 | const blobUrl = blobUrls[i]; 77 | const filePath = files[i]; 78 | 79 | try { 80 | const fileBuffer = fs.readFileSync(filePath); 81 | 82 | const uploadRes = await fetch(blobUrl, { 83 | method: 'PUT', 84 | headers: { 85 | 'x-ms-blob-type': 'BlockBlob', 86 | 'x-ms-meta-filename': path.basename(filePath), 87 | 'x-ms-meta-myownprop': 'mytestprop', 88 | 'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' 89 | }, 90 | body: fileBuffer 91 | }); 92 | 93 | if (uploadRes.ok) { 94 | console.log(`Successfully uploaded ${path.basename(filePath)} to blob URL ${i + 1}`); 95 | } else { 96 | console.error(`Failed to upload ${path.basename(filePath)}:`, uploadRes.status, uploadRes.statusText); 97 | } 98 | } catch (err) { 99 | console.error(`Error uploading ${path.basename(filePath)}:`, err); 100 | } 101 | } 102 | } 103 | 104 | // Step 3: Add the complete job endpoint URL as the first fetch argument: 105 | const completeRes = await fetch(``, { 106 | method: 'POST', 107 | headers: { 108 | 'Authorization': `Bearer ${accessToken}`, 109 | 'Accept': 'application/json', 110 | 'Content-Type': 'application/json' 111 | }, 112 | body: JSON.stringify(body) 113 | }); 114 | 115 | return {jobId: jobId, received: i}; 116 | 117 | } 118 | 119 | } catch (err) { 120 | console.error('getAgreements failed:', err); 121 | throw err; 122 | } 123 | 124 | return {ok: true} 125 | } 126 | 127 | export async function bulkUploadStatus({ jobId, accessToken } = {}) { 128 | const accountId = process.env.DS_ACCOUNT_ID; 129 | const baseUrl = process.env.BASE_URL; 130 | 131 | // Step 4: Complete the statusCheck constant with a fetch statement to the check status endpoint: 132 | const statusCheck = await fetch(); 133 | 134 | const statusC = await statusCheck.json(); 135 | 136 | console.log(JSON.stringify(statusC, null, 2)); 137 | 138 | return {status: statusC.status} 139 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Navigator Mini Dashboard 6 | 7 | 19 | 20 | 21 |
22 |

Agreements

23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
NameIDReview StatusCategoryActions
42 | 43 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docusign-discover-workshop-2 2 | 3 | By the end of this lab you will have: 4 | 5 | * [Run the project with mock data (instant feedback).](#run-the-project-with-mock-data) 6 | 7 | * [Used the Docusign AI assistant to create an IK and generate an access token.](#create-an-integration-key-and-obtain-an-access-token-using-the-vscode-docusign-ai-assistant) 8 | 9 | * [Bulk uploaded agreement documents to Navigator using an API call.](#bulk-upload-agreements-using-the-navigator-api) 10 | 11 | * [Used the IAM SDK to get navigator agreements.](#get-agreements-using-the-navigator-sdk) 12 | 13 | * [Used the IAM SDK to delete an agreement.](#implement-delete-agreement-your-task) 14 | 15 | * [(Optional) Learned how to implement OAuth with the IAM SDK.](#add-oauth-optional-advanced) 16 | 17 | * [Created and tested Connect webhooks for Navigator events.](#create-and-test-connect-webhooks) 18 | 19 | * [Made requests to the Docusign MCP server.](#connect-to-the-docusign-mcp-server) 20 | 21 | 22 | # Run the project with mock data 23 | 24 | 1. Clone the [project repo](https://github.com/docusign/docusign-discover-workshop-2) into VS Code. 25 | 26 | ```shell 27 | git clone https://github.com/docusign/docusign-discover-workshop-2.git 28 | ``` 29 | 30 | 2. Install dependencies: 31 | 32 | ```shell 33 | npm install 34 | ``` 35 | 36 | 3. Copy the `example.env` file to a new file named `.env`. 37 | 38 | 4. Start the server in development mode: 39 | 40 | 41 | ```shell 42 | npm run dev 43 | ``` 44 | 45 | 4. Open [http://localhost:3000](http://localhost:3000) in your browser. 46 | 47 | * You should see a list of **agreements**. 48 | * These are coming from a **Postman mock server**, not your real Docusign account. 49 | 50 | # Create an integration key and obtain an access token using the VSCode Docusign AI Assistant 51 | 52 | [Follow instructions here](./oauthWithAIAssistant.md) 53 | 54 | * Open your .env file and update the variable BASE\_PATH to the value [api-d.docusign.com](http://api-d.docusign.com). 55 | 56 | # Bulk upload agreements using the Navigator API 57 | 58 | Endpoints detailed in [bulkUploadWithAPI.md](./bulkUploadWithAPI.md). 59 | 60 | ## Create bulk upload job 61 | 62 | 1. The `try` statement includes an empty `body` constant. Add the following as the value of `body`: 63 | 64 | ```json 65 | { 66 | "job_name": "test_name", 67 | "expected_number_of_docs": 2, 68 | "language": "en_us" 69 | } 70 | ``` 71 | 72 | 2. The `res` constant uses a `fetch` statement. Add the URL for the [create job endpoint](./bulkUploadWithAPI.md#bulk-upload-steps) as the first argument. 73 | 74 | 3. The `completeRes` constant uses a `fetch` statement. Add the URL for the [complete job endpoint](./bulkUploadWithAPI.md#bulk-upload-steps) as the first argument. 75 | 76 | 4. The `bulkUploadStatus` function makes a request to check the status of `jobId`. Complete the `statusCheck` constant with a fetch statement to the [check status endpoint](./bulkUploadWithAPI.md#check-bulk-upload-status) that includes `Authorization`, `Accept`, and `Content-Type` headers. 77 | 78 | 5. Click the **Bulk Upload** button. You should see a modal confirming successful upload of 2 agreements. 79 | 80 | 6. Click the **Check Status** button. You should see a modal providing the status of the agreement upload and processing. Status should report complete after about 60 seconds. 81 | 82 | # Get agreements using the Navigator SDK 83 | 84 | The code currently fetches agreements using a REST API call. 85 | [`getAgreements.js`](./src/getAgreements.js) 86 | 87 | Replace this with the equivalent SDK call to Navigator: 88 | 89 | ```javascript 90 | client.navigator.agreements.getAgreementsList({ accountId }); 91 | ``` 92 | 93 | [API reference](https://developers.docusign.com/docs/navigator-api/reference/navigator/agreements/getagreementslist/) 94 | 95 | [SDK Documentation](https://developers.docusign.com/docs/sdks/iam-typescript/) 96 | 97 | **TIP** it is expected to get a response validation error from the SDK. Use the following workaround: 98 | 99 | ```javascript 100 | } catch (err) { 101 | if (err?.message?.includes('Response validation failed') && err?.rawValue) { 102 | console.warn('getAgreements: response validation failed — using rawValue fallback'); 103 | data = err.rawValue; 104 | } else { 105 | ... 106 | ``` 107 | 108 | Restart your server and refresh the browser — you should now see **real agreements from your Docusign account**. 109 | 110 | ## Implement agreement filtering (optional/advanced) 111 | 112 | You can filter agreements by properties such as status, expiration date, party name, etc. The full list of available query parameters is documented on 113 | the [API reference page.](https://developers.docusign.com/docs/navigator-api/reference/navigator/agreements/getagreementslist/) 114 | 115 | Try extending your SDK method calls with additional query parameters. Experiment with different combinations to see how you can sort and filter agreements in ways that match the requirements of various use cases. 116 | 117 | ```javascript 118 | let options = { 119 | accountId: accountId, 120 | limit: 10, 121 | "effective_date[gte]": "2015-01-01", 122 | sort: "effective_date", 123 | direction: "asc" 124 | }; 125 | 126 | client.navigator.agreements.getAgreementsList( options ); 127 | ``` 128 | 129 | # Implement “Delete Agreement” (your task) 130 | 131 | * In the project there’s a stubbed-out function for **Delete Agreement** using the SDK. 132 | [`deleteAgreement.js`](./src/deleteAgreement.js) 133 | 134 | Your job is to complete that function: 135 | 136 | ```javascript 137 | client.navigator.agreements.deleteAgreement({ accountId, agreementId }); 138 | ``` 139 | 140 | [API reference](https://developers.docusign.com/docs/navigator-api/reference/navigator/agreements/deleteagreement/) 141 | 142 | * Test it by deleting one of your agreements. 143 | 144 | # Add OAuth (Optional, advanced) 145 | [Auth examples](https://github.com/docusign/docusign-iam-typescript-client/tree/main/auth-examples) 146 | 147 | * Right now you pasted in an access token manually. 148 | 149 | * For a production app, you should implement an OAuth flow to fetch/refresh tokens automatically. 150 | 151 | * Choose either: 152 | 153 | * **Authorization Code Grant** (browser \+ server flow) 154 | 155 | * **JWT Grant** (server-to-server) 156 | 157 | * The project has an [`auth.js`](./src/auth.js) file stubbed out for you to implement. 158 | 159 | # Create and Test Connect Webhooks 160 | 161 | Events detailed in [createWebhooks.md](./createWebhooks.md). 162 | 163 | 1. Visit [https://apps-d.docusign.com/admin/connect/](https://apps-d.docusign.com/admin/connect/). 164 | 165 | 1. Click **Add Configuration** > **Custom**. 166 | 167 | 1. In the Name box, type **Navigator API test** or another configuration name of your choosing. 168 | 169 | 1. In a new tab, open [https://webhook.site](https://webhook.site), then click the generated URL to copy it. 170 | 171 | 1. Return to your Custom Connect configuration, then in the **URL to Publish** box paste the URL you copied. 172 | 173 | 1. In the Trigger Events section, open the Navigator list and check all 5 events. 174 | 175 | 1. Click **Add Configuration**. 176 | 177 | 1. In a new tab, open [https://apps-d.docusign.com/send/documents](https://apps-d.docusign.com/send/documents), then in the sidebar, click **Completed**. 178 | 179 | 1. Click an agreement with the icon for AI suggestions (purple star), click **Review All**, approve one of the AI-suggested values, then click **Save**. 180 | 181 | 1. On the webhook.site tab, review the **agreement-extractions-reviewed** message. 182 | 183 | 1. In your open agreement, click **Review All**, approve all remaining AI-suggested values, then click **Save**. 184 | 185 | 1. On the webhook.site tab, review the **agreement-reviews-complete** message. 186 | 187 | 1. In your open agreement, click the edit icon (pencil), change a data value, then click **Save**. 188 | 189 | 1. On the webhook.site tab, review the **agreement-updated** message. 190 | 191 | 1. Return to your open agreement close it, in the agreements list click the vertical ellipsis (**⋮**) for the agreement, click **Remove**, then click **Remove Agreement.** 192 | 193 | 1. On the webhook.site tab, review the **agreement-deleted** message. 194 | 195 | # Connect to the Docusign MCP server 196 | 197 | Check out the `discover-mcp-workshop` branch of this repo. You'll need to work in this branch to ensure clean context for the agent you're building. 198 | 199 | ```shell 200 | git checkout discover-mcp-workshop 201 | ``` 202 | 203 | See [Docusign MCP Workshop: AI-Powered Agreement Analysis](https://github.com/docusign/docusign-discover-workshop-2/blob/discover-mcp-workshop/README.md) for your instructions. 204 | 205 | 206 | 207 | --------------------------------------------------------------------------------