JavaScript API Client
A complete, production-ready NotaryCam API client for Node.js applications.
Installation
npm install axios form-data
Complete API Client
const axios = require('axios');
const FormData = require('form-data');
/**
* NotaryCam API Client for Node.js
* Handles authentication, requests, and error handling
*/
class NotaryCamAPI {
constructor(baseUrl = '{BASE_URL}', credentials = null) {
this.baseUrl = baseUrl.replace(/\/$/, '');
this.credentials = credentials;
this.token = null;
this.tokenExpiry = null;
}
/**
* Ensure we have a valid authentication token
*/
async ensureAuthenticated() {
const now = Date.now();
// Refresh token if expired or not set
if (!this.token || !this.tokenExpiry || now >= this.tokenExpiry) {
await this.authorize();
}
}
/**
* Step 1: Authenticate and get JWT token
*/
async authorize(partnerId, apiKey, apiSecret) {
const creds = partnerId ? { partnerId, apiKey, apiSecret } : this.credentials;
if (!creds) {
throw new Error('Credentials required for authentication');
}
const response = await axios.post(
`${this.baseUrl}/api/v4/authorize/`,
creds,
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
this.token = response.data.token;
// Set expiry to 23 hours from now (1 hour buffer before actual 24h expiry)
this.tokenExpiry = Date.now() + (23 * 60 * 60 * 1000);
return response.data;
}
/**
* Make an authenticated API request
*/
async makeRequest(method, endpoint, data = null, options = {}) {
await this.ensureAuthenticated();
const config = {
method,
url: `${this.baseUrl}${endpoint}`,
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json',
...options.headers
},
...options
};
if (data) {
if (method === 'GET') {
config.params = data;
} else {
config.data = data;
}
}
return axios(config);
}
// ==========================================
// USER MANAGEMENT
// ==========================================
/**
* Step 2: Create a user
*/
async createUser(userData) {
const response = await this.makeRequest('POST', '/api/v4/users', userData);
return response.data;
}
/**
* Get user details by ID
*/
async getUser(userId) {
const response = await this.makeRequest('GET', `/api/v4/users/${userId}`);
return response.data;
}
/**
* Update user details
*/
async updateUser(userId, userData) {
const response = await this.makeRequest('PUT', `/api/v4/users/${userId}`, userData);
return response.data;
}
/**
* Search for users by email or ID
*/
async searchUsers(criteria) {
const response = await this.makeRequest('POST', '/api/v4/users/search', criteria);
return response.data;
}
// ==========================================
// TRANSACTION MANAGEMENT
// ==========================================
/**
* Step 3: Create a transaction
*/
async createTransaction(transactionData) {
const response = await this.makeRequest('POST', '/api/v4/transactions/', transactionData);
return response.data;
}
/**
* Get transaction details
*/
async getTransaction(transactionId) {
const response = await this.makeRequest('GET', `/api/v4/transactions/${transactionId}`);
return response.data;
}
/**
* Search transactions
*/
async searchTransactions(criteria) {
const response = await this.makeRequest('POST', '/api/v4/transactions/search', criteria);
return response.data;
}
/**
* Step 6: Activate a transaction
*/
async activateTransaction(transactionId) {
const response = await this.makeRequest('PUT', `/api/v4/transactions/${transactionId}/activate`, {});
return response.data;
}
/**
* Deactivate a transaction
*/
async deactivateTransaction(transactionId) {
const response = await this.makeRequest('PUT', `/api/v4/transactions/${transactionId}/deactivate`, {});
return response.data;
}
/**
* Archive a transaction
*/
async archiveTransaction(transactionId) {
const response = await this.makeRequest('PUT', `/api/v4/transactions/${transactionId}/archive`, {});
return response.data;
}
// ==========================================
// PARTICIPANT MANAGEMENT
// ==========================================
/**
* Add participants to a transaction
*/
async addParticipants(transactionId, participants) {
const response = await this.makeRequest(
'POST',
`/api/v4/transactions/${transactionId}/participants`,
{ participants }
);
return response.data;
}
/**
* Remove a participant from a transaction
*/
async removeParticipant(transactionId, userId) {
const response = await this.makeRequest(
'PUT',
`/api/v4/transactions/${transactionId}/participants/${userId}/archive`,
{}
);
return response.data;
}
// ==========================================
// DOCUMENT MANAGEMENT
// ==========================================
/**
* Step 4a: Upload documents from files
*/
async uploadDocuments(transactionId, documents) {
await this.ensureAuthenticated();
const formData = new FormData();
// Add each document file
documents.forEach((doc) => {
formData.append('documents', doc.fileBuffer, {
filename: doc.fileName,
contentType: doc.contentType || 'application/pdf'
});
});
// Add document metadata
const documentData = documents.map(doc => ({
type: doc.type,
fileName: doc.fileName
}));
formData.append('documentData', JSON.stringify(documentData));
const response = await axios.post(
`${this.baseUrl}/api/v4/transactions/${transactionId}/documents`,
formData,
{
headers: {
'Authorization': `Bearer ${this.token}`,
...formData.getHeaders()
}
}
);
return response.data;
}
/**
* Step 4b: Upload documents from URLs
*/
async uploadDocumentsFromUrls(transactionId, documents) {
const response = await this.makeRequest(
'POST',
`/api/v4/transactions/${transactionId}/documents/url`,
{ documents }
);
return response.data;
}
/**
* Get all documents for a transaction
*/
async getTransactionDocuments(transactionId) {
const response = await this.makeRequest('GET', `/api/v4/transactions/${transactionId}/documents`);
return response.data;
}
/**
* Get document details
*/
async getDocumentDetails(transactionId, documentId) {
const response = await this.makeRequest(
'GET',
`/api/v4/transactions/${transactionId}/documents/${documentId}`
);
return response.data;
}
/**
* Download a document
*/
async downloadDocument(transactionId, documentId) {
const response = await this.makeRequest(
'GET',
`/api/v4/documents/${transactionId}/${documentId}`,
null,
{ responseType: 'arraybuffer' }
);
return response.data;
}
/**
* Download all documents as ZIP
*/
async downloadAllDocuments(transactionId) {
const response = await this.makeRequest(
'GET',
`/api/v4/transactions/${transactionId}/downloadAll`,
null,
{ responseType: 'arraybuffer' }
);
return response.data;
}
/**
* Archive a document
*/
async archiveDocument(transactionId, documentId) {
const response = await this.makeRequest(
'PUT',
`/api/v4/transactions/${transactionId}/documents/${documentId}/archive`,
{}
);
return response.data;
}
// ==========================================
// METADATA MANAGEMENT
// ==========================================
/**
* Step 5: Set transaction metadata
*/
async setTransactionMetadata(transactionId, metadata) {
const response = await this.makeRequest(
'PUT',
`/api/v4/transactions/${transactionId}/metadata`,
metadata
);
return response.data;
}
/**
* Get transaction metadata
*/
async getTransactionMetadata(transactionId) {
const response = await this.makeRequest('GET', `/api/v4/transactions/${transactionId}/metadata`);
return response.data;
}
// ==========================================
// AUDIT & RECORDINGS
// ==========================================
/**
* Get transaction audit trail
*/
async getAuditTrail(transactionId) {
const response = await this.makeRequest('GET', `/api/v4/journal/${transactionId}/auditTrail`);
return response.data;
}
/**
* Get recording download link
*/
async getRecordingDownloadLink(transactionId, recordingId) {
const response = await this.makeRequest(
'GET',
`/api/v4/transactions/${transactionId}/recordings/${recordingId}`
);
return response.data;
}
}
module.exports = NotaryCamAPI;
Usage Examples
Basic Setup
const NotaryCamAPI = require('./notarycam-api');
require('dotenv').config();
// Initialize with credentials
const api = new NotaryCamAPI('{BASE_URL}', {
partnerId: process.env.NOTARYCAM_PARTNER_ID,
apiKey: process.env.NOTARYCAM_API_KEY,
apiSecret: process.env.NOTARYCAM_API_SECRET
});
// Or initialize and authenticate separately
const api = new NotaryCamAPI();
await api.authorize(partnerId, apiKey, apiSecret);
Complete Transaction
async function createTransaction() {
try {
// Authentication happens automatically
// Create users
const user1 = await api.createUser({
departments: [process.env.NOTARYCAM_DEPARTMENT_ID],
email: 'signer@example.com',
firstName: 'John',
lastName: 'Doe',
phone: '+15551234567',
dateOfBirth: '1985-06-15T00:00:00.000Z',
address: {
country: 'US',
street: '123 Main St',
city: 'San Francisco',
state: 'CA',
postalCode: '94102'
}
});
// Create transaction
const transaction = await api.createTransaction({
enterprise: process.env.NOTARYCAM_ENTERPRISE_ID, // Optional
department: process.env.NOTARYCAM_DEPARTMENT_ID, // Required
participants: [
{ user: user1.user._id, role: 'signer' }
],
workflow: process.env.NOTARYCAM_WORKFLOW_ID // Optional
});
const txId = transaction.transaction._id;
// Upload documents
await api.uploadDocumentsFromUrls(txId, [
{
url: 'https://example.com/document.pdf',
fileName: 'contract.pdf',
type: 'notarize'
}
]);
// Set metadata
await api.setTransactionMetadata(txId, {
notes: ['API integration test'],
customFields: [
{ name: 'Source', value: 'Node.js Client' }
]
});
// Activate
await api.activateTransaction(txId);
console.log(' Transaction created:', txId);
console.log('Room URLs:', transaction.transaction.participants.map(p => p.roomURL));
} catch (error) {
console.error('Error:', error.response?.data || error.message);
}
}
createTransaction();
Error Handling
async function createTransactionWithRetry() {
const maxRetries = 3;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const transaction = await api.createTransaction(data);
return transaction;
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
const isRetryable =
error.code === 'ECONNRESET' ||
error.code === 'ETIMEDOUT' ||
error.response?.status >= 500;
if (!isRetryable) {
throw error;
}
const delay = Math.pow(2, attempt - 1) * 1000;
console.log(`Retry ${attempt}/${maxRetries} after ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
Search Users Before Creating
async function getOrCreateUser(email, userData) {
// Search for existing user
const searchResult = await api.searchUsers({ emails: [email] });
if (searchResult.users.length > 0) {
console.log('User exists:', searchResult.users[0]._id);
return searchResult.users[0];
}
// Create new user
const newUser = await api.createUser(userData);
console.log('User created:', newUser.user._id);
return newUser.user;
}
Parallel User Creation
async function createMultipleUsers(userDataArray) {
const users = await Promise.all(
userDataArray.map(userData => api.createUser(userData))
);
return users.map(response => response.user);
}
Environment Variables
Create a .env file:
# NotaryCam API Credentials
NOTARYCAM_PARTNER_ID=your-partner-id
NOTARYCAM_API_KEY=your-api-key
NOTARYCAM_API_SECRET=your-api-secret
# Organization IDs
NOTARYCAM_ENTERPRISE_ID=your-enterprise-id
NOTARYCAM_DEPARTMENT_ID=your-department-id
NOTARYCAM_WORKFLOW_ID=your-workflow-id
# Environment
NOTARYCAM_BASE_URL={BASE_URL}
TypeScript Support
Add type definitions:
interface NotaryCamCredentials {
partnerId: string;
apiKey: string;
apiSecret: string;
}
interface CreateUserRequest {
departments: string[];
email: string;
firstName: string;
lastName: string;
phone: string;
dateOfBirth?: string;
address?: {
country: string;
street: string;
city: string;
state: string;
postalCode: string;
};
}
interface CreateTransactionRequest {
enterprise?: string; // Optional - auto-derived from department
department: string; // Required
participants: Array<{
user: string;
role: string;
}>;
workflow?: string; // Optional - auto-assigned if not provided
}
declare class NotaryCamAPI {
constructor(baseUrl?: string, credentials?: NotaryCamCredentials);
authorize(partnerId: string, apiKey: string, apiSecret: string): Promise<any>;
createUser(userData: CreateUserRequest): Promise<any>;
createTransaction(data: CreateTransactionRequest): Promise<any>;
// ... other methods
}
Testing
const NotaryCamAPI = require('./notarycam-api');
describe('NotaryCamAPI', () => {
let api;
beforeEach(() => {
api = new NotaryCamAPI('{BASE_URL}');
});
test('should authenticate successfully', async () => {
const result = await api.authorize(
process.env.NOTARYCAM_PARTNER_ID,
process.env.NOTARYCAM_API_KEY,
process.env.NOTARYCAM_API_SECRET
);
expect(result.success).toBe(true);
expect(result.token).toBeDefined();
});
test('should create user', async () => {
await api.authorize(partnerId, apiKey, apiSecret);
const user = await api.createUser({
departments: ['dept-id'],
email: 'test@example.com',
firstName: 'Test',
lastName: 'User',
phone: '+15550000000'
});
expect(user.success).toBe(true);
expect(user.user._id).toBeDefined();
});
});
Production Considerations
Rate Limiting
class RateLimitedAPI extends NotaryCamAPI {
constructor(baseUrl, credentials, rateLimit = 100) {
super(baseUrl, credentials);
this.requestTimes = [];
this.rateLimit = rateLimit;
}
async checkRateLimit() {
const now = Date.now();
const oneMinuteAgo = now - 60000;
// Remove old timestamps
this.requestTimes = this.requestTimes.filter(time => time > oneMinuteAgo);
if (this.requestTimes.length >= this.rateLimit) {
const oldestRequest = this.requestTimes[0];
const delay = 60000 - (now - oldestRequest);
if (delay > 0) {
console.log(`Rate limit reached. Waiting ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
this.requestTimes.push(now);
}
async makeRequest(method, endpoint, data, options) {
await this.checkRateLimit();
return super.makeRequest(method, endpoint, data, options);
}
}
Logging
class LoggedAPI extends NotaryCamAPI {
async makeRequest(method, endpoint, data, options) {
const startTime = Date.now();
console.log(`API Request: ${method} ${endpoint}`);
try {
const response = await super.makeRequest(method, endpoint, data, options);
const duration = Date.now() - startTime;
console.log(`API Response: ${method} ${endpoint} - ${duration}ms - Success`);
return response;
} catch (error) {
const duration = Date.now() - startTime;
console.error(`API Error: ${method} ${endpoint} - ${duration}ms -`,
error.response?.status, error.message);
throw error;
}
}
}