@certysign/sdk 2.4.0 → 2.5.1

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.
@@ -0,0 +1,90 @@
1
+ /**
2
+ * client.billing — what the organisation has, and what things cost.
3
+ *
4
+ * Read-only by design. Topping up, changing plan and editing auto-top-up are done
5
+ * from the CertySign dashboard by a person, not by an API key: a key is a bearer
6
+ * token that can leak, and a leaked key must not be able to spend or to change what
7
+ * the organisation is billed.
8
+ *
9
+ * Requires the `billing:read` scope.
10
+ */
11
+ class BillingResource {
12
+ constructor(http) {
13
+ this._http = http;
14
+ }
15
+
16
+ /**
17
+ * Token balance and wallet standing.
18
+ *
19
+ * `availableBalance` is the number to spend against — `balance` includes tokens
20
+ * already reserved by operations in flight. A `status` of 'suspended' means
21
+ * chargeable calls are refused even when the balance looks healthy.
22
+ *
23
+ * @example
24
+ * const { data } = await client.billing.wallet();
25
+ * console.log(`${data.availableBalance} ${data.currency} available`);
26
+ */
27
+ wallet() {
28
+ return this._http.get('/sdk/v1/billing/wallet');
29
+ }
30
+
31
+ /**
32
+ * What an operation will cost, and whether you can currently cover it.
33
+ *
34
+ * Call this BEFORE a large batch. One answer up front beats discovering
35
+ * INSUFFICIENT_BALANCE halfway through, with some documents signed and charged
36
+ * and others not.
37
+ *
38
+ * @param {object} opts
39
+ * @param {string} opts.operationType e.g. 'document_signing', 'certificate_issuance'
40
+ * @param {string} [opts.operationSubType] e.g. 'ADVANCED', 'ADES'
41
+ * @param {number} [opts.quantity=1] how many — the total is priced for all of them
42
+ * @param {number} [opts.additionalSigners=0]
43
+ *
44
+ * @example
45
+ * const { data } = await client.billing.quote({
46
+ * operationType: 'document_signing',
47
+ * operationSubType: 'ADVANCED',
48
+ * quantity: documents.length,
49
+ * });
50
+ * if (!data.canAfford) throw new Error(`Need ${data.tokensTotal}, have ${data.availableBalance}`);
51
+ */
52
+ quote({ operationType, operationSubType, quantity = 1, additionalSigners = 0 } = {}) {
53
+ if (!operationType) throw new Error('billing.quote: operationType is required');
54
+ const qs = new URLSearchParams({
55
+ operationType,
56
+ ...(operationSubType && { operationSubType }),
57
+ quantity: String(quantity),
58
+ additionalSigners: String(additionalSigners),
59
+ });
60
+ return this._http.get(`/sdk/v1/billing/quote?${qs.toString()}`);
61
+ }
62
+
63
+ /**
64
+ * Wallet history — what was charged, for what, and when.
65
+ *
66
+ * @param {object} [opts]
67
+ * @param {number} [opts.limit=50] capped at 200
68
+ * @param {number} [opts.skip=0]
69
+ * @param {string} [opts.type] filter by transaction type
70
+ * @param {string} [opts.status] filter by status
71
+ */
72
+ transactions({ limit = 50, skip = 0, type, status } = {}) {
73
+ const qs = new URLSearchParams({ limit: String(limit), skip: String(skip) });
74
+ if (type) qs.set('type', type);
75
+ if (status) qs.set('status', status);
76
+ return this._http.get(`/sdk/v1/billing/transactions?${qs.toString()}`);
77
+ }
78
+
79
+ /** The subscription plan and the feature flags that come with it. */
80
+ plan() {
81
+ return this._http.get('/sdk/v1/billing/plan');
82
+ }
83
+
84
+ /** The full price list, for showing costs without a call per item. */
85
+ pricing() {
86
+ return this._http.get('/sdk/v1/billing/pricing');
87
+ }
88
+ }
89
+
90
+ module.exports = { BillingResource };
@@ -1,249 +1,306 @@
1
- /**
2
- * @fileoverview EnvelopeResource — full envelope lifecycle management
3
- *
4
- * Covers the complete envelope CRUD plus document management:
5
- * - create() POST /sdk/v1/envelopes
6
- * - get() GET /sdk/v1/envelopes/:id
7
- * - list() GET /sdk/v1/envelopes
8
- * - uploadDocuments() POST /sdk/v1/envelopes/:id/documents
9
- * - send() POST /sdk/v1/envelopes/:id/send
10
- * - sign() POST /sdk/v1/envelopes/:id/sign
11
- * - getDocument() GET /sdk/v1/envelopes/:id/documents/:docId
12
- * - getAuditTrail() GET /sdk/v1/envelopes/:id/audit
13
- */
14
-
15
- 'use strict';
16
-
17
- const FormData = require('form-data');
18
-
19
- class EnvelopeResource {
20
- /** @param {import('../lib/HttpClient').HttpClient} http */
21
- constructor(http) {
22
- this._http = http;
23
- }
24
-
25
- // ── Create ─────────────────────────────────────────────────────────────────
26
-
27
- /**
28
- * Create a new signing envelope.
29
- *
30
- * Envelopes are containers for documents and signature requests.
31
- * After creation, upload documents (uploadDocuments), then send
32
- * the envelope (send) to make it ready for signing.
33
- *
34
- * @param {CreateEnvelopeOptions} options
35
- * @returns {Promise<EnvelopeResult>}
36
- *
37
- * @example
38
- * const { data } = await client.envelopes.create({
39
- * title: 'NHIF Reimbursement Claim Q1-2026',
40
- * signers: [{
41
- * name: 'Dr. Grace Mwangi',
42
- * email: 'g.mwangi@nhif.or.ke',
43
- * role: 'primary'
44
- * }],
45
- * metadata: { claimPeriod: 'Q1-2026', department: 'Reimbursements' }
46
- * });
47
- * const envelopeId = data.envelope._id;
48
- */
49
- create(options = {}) {
50
- const { title, signers = [], message, metadata = {} } = options;
51
- if (!title) throw new Error('envelopes.create: title is required');
52
- return this._http.post('/sdk/v1/envelopes', {
53
- data: { title, signers, message, metadata }
54
- });
55
- }
56
-
57
- // ── Get ────────────────────────────────────────────────────────────────────
58
-
59
- /**
60
- * Get a single envelope by ID with full status, signers, and document list.
61
- *
62
- * @param {string} envelopeId
63
- * @returns {Promise<EnvelopeResult>}
64
- */
65
- get(envelopeId) {
66
- if (!envelopeId) throw new Error('envelopes.get: envelopeId is required');
67
- return this._http.get(`/sdk/v1/envelopes/${encodeURIComponent(envelopeId)}`);
68
- }
69
-
70
- // ── List ───────────────────────────────────────────────────────────────────
71
-
72
- /**
73
- * List envelopes for the authenticated tenant.
74
- *
75
- * @param {ListEnvelopesOptions} [options]
76
- * @returns {Promise<ListEnvelopesResult>}
77
- *
78
- * @example
79
- * const { data } = await client.envelopes.list({ status: 'completed', limit: 20 });
80
- * data.envelopes.forEach(env => console.log(env._id, env.status));
81
- */
82
- list(options = {}) {
83
- const { status, page = 1, limit = 20, search } = options;
84
- return this._http.get('/sdk/v1/envelopes', {
85
- params: { status, page, limit, search }
86
- });
87
- }
88
-
89
- // ── Upload Documents ───────────────────────────────────────────────────────
90
-
91
- /**
92
- * Upload one or more PDF documents to an existing envelope.
93
- *
94
- * Must be called after create() and before send().
95
- *
96
- * @param {string} envelopeId
97
- * @param {DocumentUpload[]} documents
98
- * @returns {Promise<UploadResult>}
99
- *
100
- * @example
101
- * await client.envelopes.uploadDocuments('env_abc123', [
102
- * { data: fs.readFileSync('./report.pdf'), filename: 'Q1-report.pdf' }
103
- * ]);
104
- */
105
- uploadDocuments(envelopeId, documents) {
106
- if (!envelopeId) throw new Error('envelopes.uploadDocuments: envelopeId is required');
107
- if (!documents?.length) throw new Error('envelopes.uploadDocuments: documents array is required');
108
-
109
- const form = new FormData();
110
- for (const doc of documents) {
111
- const buf = doc.data;
112
- const name = doc.filename || 'document.pdf';
113
- if (Buffer.isBuffer(buf)) {
114
- form.append('documents', buf, { filename: name, contentType: 'application/pdf' });
115
- } else if (buf && typeof buf.pipe === 'function') {
116
- form.append('documents', buf, { filename: name, contentType: 'application/pdf' });
117
- } else {
118
- throw new Error(`Document "${name}" must be a Buffer or ReadStream`);
119
- }
120
- }
121
-
122
- return this._http.post(`/sdk/v1/envelopes/${encodeURIComponent(envelopeId)}/documents`, {
123
- formData: form
124
- });
125
- }
126
-
127
- // ── Send ───────────────────────────────────────────────────────────────────
128
-
129
- /**
130
- * Transition an envelope to 'sent' status, making it ready for signing.
131
- *
132
- * @param {string} envelopeId
133
- * @returns {Promise<EnvelopeResult>}
134
- */
135
- send(envelopeId) {
136
- if (!envelopeId) throw new Error('envelopes.send: envelopeId is required');
137
- return this._http.post(`/sdk/v1/envelopes/${encodeURIComponent(envelopeId)}/send`);
138
- }
139
-
140
- // ── Sign ───────────────────────────────────────────────────────────────────
141
-
142
- /**
143
- * Apply a cryptographic signature to all documents in the envelope.
144
- *
145
- * This is the core signing step. For SDK requests, interactive 2FA is
146
- * bypassed — the API key alone authorises the signature.
147
- *
148
- * The resulting signatures are PAdES-LTV compliant (PDF Advanced Electronic
149
- * Signatures with Long-Term Validation).
150
- *
151
- * @param {string} envelopeId
152
- * @param {SignEnvelopeOptions} [options]
153
- * @returns {Promise<SignResult>}
154
- *
155
- * @example
156
- * const result = await client.envelopes.sign('env_abc123', {
157
- * reason: 'Claims approval — NHIF Kenya',
158
- * location: 'Nairobi, Kenya'
159
- * });
160
- * console.log(result.data.signatures[0].certificate.serialNumber);
161
- */
162
- sign(envelopeId, options = {}) {
163
- if (!envelopeId) throw new Error('envelopes.sign: envelopeId is required');
164
- const { reason = 'Digital signature', location = 'Nairobi, Kenya' } = options;
165
- return this._http.post(`/sdk/v1/envelopes/${encodeURIComponent(envelopeId)}/sign`, {
166
- data: { reason, location }
167
- });
168
- }
169
-
170
- // ── Download Document ──────────────────────────────────────────────────────
171
-
172
- /**
173
- * Download a signed document as a Buffer.
174
- *
175
- * @param {string} envelopeId
176
- * @param {string} documentId
177
- * @returns {Promise<Buffer>} Raw PDF bytes
178
- *
179
- * @example
180
- * const pdfBuffer = await client.envelopes.getDocument('env_abc123', 'doc_xyz789');
181
- * fs.writeFileSync('./signed-claim.pdf', pdfBuffer);
182
- */
183
- async getDocument(envelopeId, documentId) {
184
- if (!envelopeId) throw new Error('envelopes.getDocument: envelopeId is required');
185
- if (!documentId) throw new Error('envelopes.getDocument: documentId is required');
186
-
187
- const response = await this._http.get(
188
- `/sdk/v1/envelopes/${encodeURIComponent(envelopeId)}/documents/${encodeURIComponent(documentId)}`,
189
- { responseType: 'arraybuffer' }
190
- );
191
- return Buffer.isBuffer(response) ? response : Buffer.from(response);
192
- }
193
-
194
- // ── Audit Trail ────────────────────────────────────────────────────────────
195
-
196
- /**
197
- * Retrieve the cryptographically verifiable audit trail for an envelope.
198
- *
199
- * The audit chain is a Merkle-linked log of all envelope events:
200
- * - created, document_uploaded, sent, signed, certificate_issued, completed
201
- *
202
- * Each entry includes a SHA-256 event hash and chain hash linking it to
203
- * the previous entry, making the log tamper-evident.
204
- *
205
- * @param {string} envelopeId
206
- * @returns {Promise<AuditTrailResult>}
207
- *
208
- * @example
209
- * const { data } = await client.envelopes.getAuditTrail('env_abc123');
210
- * data.auditTrail.forEach(event => {
211
- * console.log(event.timestamp, event.action, event.eventHash);
212
- * });
213
- * if (data.chainIntegrity.valid) console.log('Audit chain intact');
214
- */
215
- getAuditTrail(envelopeId) {
216
- if (!envelopeId) throw new Error('envelopes.getAuditTrail: envelopeId is required');
217
- return this._http.get(`/sdk/v1/envelopes/${encodeURIComponent(envelopeId)}/audit`);
218
- }
219
- }
220
-
221
- /**
222
- * @typedef {Object} CreateEnvelopeOptions
223
- * @property {string} title - Envelope title
224
- * @property {{ name: string, email?: string, role?: string }[]} [signers]
225
- * @property {string} [message] - Message to signers
226
- * @property {Object} [metadata]
227
- */
228
-
229
- /**
230
- * @typedef {Object} DocumentUpload
231
- * @property {Buffer|import('fs').ReadStream} data
232
- * @property {string} [filename]
233
- */
234
-
235
- /**
236
- * @typedef {Object} SignEnvelopeOptions
237
- * @property {string} [reason]
238
- * @property {string} [location]
239
- */
240
-
241
- /**
242
- * @typedef {Object} ListEnvelopesOptions
243
- * @property {'draft'|'sent'|'in_progress'|'completed'|'cancelled'} [status]
244
- * @property {number} [page]
245
- * @property {number} [limit]
246
- * @property {string} [search]
247
- */
248
-
249
- module.exports = { EnvelopeResource };
1
+ /**
2
+ * @fileoverview EnvelopeResource — full envelope lifecycle management
3
+ *
4
+ * Covers the complete envelope CRUD plus document management:
5
+ * - create() POST /sdk/v1/envelopes
6
+ * - get() GET /sdk/v1/envelopes/:id
7
+ * - list() GET /sdk/v1/envelopes
8
+ * - uploadDocuments() POST /sdk/v1/envelopes/:id/documents
9
+ * - send() POST /sdk/v1/envelopes/:id/send
10
+ * - sign() POST /sdk/v1/envelopes/:id/sign
11
+ * - getDocument() GET /sdk/v1/envelopes/:id/documents/:docId
12
+ * - getAuditTrail() GET /sdk/v1/envelopes/:id/audit
13
+ */
14
+
15
+ 'use strict';
16
+
17
+ const FormData = require('form-data');
18
+
19
+ class EnvelopeResource {
20
+ /** @param {import('../lib/HttpClient').HttpClient} http */
21
+ constructor(http) {
22
+ this._http = http;
23
+ }
24
+
25
+ // ── Create ─────────────────────────────────────────────────────────────────
26
+
27
+ /**
28
+ * Create a new signing envelope.
29
+ *
30
+ * Envelopes are containers for documents and signature requests.
31
+ * After creation, upload documents (uploadDocuments), then send
32
+ * the envelope (send) to make it ready for signing.
33
+ *
34
+ * @param {CreateEnvelopeOptions} options
35
+ * @returns {Promise<EnvelopeResult>}
36
+ *
37
+ * @example
38
+ * const { data } = await client.envelopes.create({
39
+ * name: 'NHIF Reimbursement Claim Q1-2026',
40
+ * recipients: [{
41
+ * name: 'Dr. Grace Mwangi',
42
+ * email: 'g.mwangi@nhif.or.ke',
43
+ * role: 'primary'
44
+ * }],
45
+ * metadata: { claimPeriod: 'Q1-2026', department: 'Reimbursements' }
46
+ * });
47
+ * const envelopeId = data.envelope._id;
48
+ */
49
+ create(options = {}) {
50
+ // The API reads `name` and `recipients`. This used to post `title` and `signers`,
51
+ // which the server simply ignored — producing an unnamed envelope with nobody on
52
+ // it, which then failed at send time with NO_SIGNERS and no hint as to why. Both
53
+ // spellings are accepted so existing callers keep working, and both are mapped to
54
+ // what the server actually reads.
55
+ const {
56
+ title, name = title,
57
+ signers, recipients = signers || [],
58
+ message, description = message,
59
+ metadata = {},
60
+ signingOrder, signingSequence, requires2FA, requiresBiometric,
61
+ biometricFallback, expiresAt,
62
+ } = options;
63
+
64
+ if (!name) throw new Error('envelopes.create: name (or title) is required');
65
+
66
+ return this._http.post('/sdk/v1/envelopes', {
67
+ data: {
68
+ name,
69
+ ...(description !== undefined && { description }),
70
+ recipients,
71
+ metadata,
72
+ ...(signingOrder !== undefined && { signingOrder }),
73
+ ...(signingSequence !== undefined && { signingSequence }),
74
+ ...(requires2FA !== undefined && { requires2FA }),
75
+ ...(requiresBiometric !== undefined && { requiresBiometric }),
76
+ ...(biometricFallback !== undefined && { biometricFallback }),
77
+ ...(expiresAt !== undefined && { expiresAt }),
78
+ },
79
+ });
80
+ }
81
+
82
+ // ── Send ───────────────────────────────────────────────────────────────────
83
+
84
+ /**
85
+ * Send a draft envelope to its recipients for signature.
86
+ *
87
+ * Requires the `envelope:send` scope. The envelope must be a draft with at least
88
+ * one document and one recipient — envelopes produced by the signing endpoints are
89
+ * already complete and cannot be sent.
90
+ *
91
+ * @example
92
+ * await client.envelopes.send(envelopeId);
93
+ */
94
+ send(envelopeId) {
95
+ if (!envelopeId) throw new Error('envelopes.send: envelopeId is required');
96
+ return this._http.post(`/sdk/v1/envelopes/${encodeURIComponent(envelopeId)}/send`, { data: {} });
97
+ }
98
+
99
+ /**
100
+ * Verify an envelope's audit hash chain.
101
+ *
102
+ * Requires the `audit:verify` scope. Works on any envelope, however it was
103
+ * produced — an immediate signing call or a collected signing workflow.
104
+ *
105
+ * @example
106
+ * const { data } = await client.envelopes.verifyAuditChain(envelopeId);
107
+ * if (!data.valid) throw new Error('Audit trail has been altered');
108
+ */
109
+ verifyAuditChain(envelopeId) {
110
+ if (!envelopeId) throw new Error('envelopes.verifyAuditChain: envelopeId is required');
111
+ return this._http.post(`/sdk/v1/envelopes/${encodeURIComponent(envelopeId)}/audit/verify`, { data: {} });
112
+ }
113
+
114
+ // ── Get ────────────────────────────────────────────────────────────────────
115
+
116
+ /**
117
+ * Get a single envelope by ID with full status, signers, and document list.
118
+ *
119
+ * @param {string} envelopeId
120
+ * @returns {Promise<EnvelopeResult>}
121
+ */
122
+ get(envelopeId) {
123
+ if (!envelopeId) throw new Error('envelopes.get: envelopeId is required');
124
+ return this._http.get(`/sdk/v1/envelopes/${encodeURIComponent(envelopeId)}`);
125
+ }
126
+
127
+ // ── List ───────────────────────────────────────────────────────────────────
128
+
129
+ /**
130
+ * List envelopes for the authenticated tenant.
131
+ *
132
+ * @param {ListEnvelopesOptions} [options]
133
+ * @returns {Promise<ListEnvelopesResult>}
134
+ *
135
+ * @example
136
+ * const { data } = await client.envelopes.list({ status: 'completed', limit: 20 });
137
+ * data.envelopes.forEach(env => console.log(env._id, env.status));
138
+ */
139
+ list(options = {}) {
140
+ const { status, page = 1, limit = 20, search } = options;
141
+ return this._http.get('/sdk/v1/envelopes', {
142
+ params: { status, page, limit, search }
143
+ });
144
+ }
145
+
146
+ // ── Upload Documents ───────────────────────────────────────────────────────
147
+
148
+ /**
149
+ * Upload one or more PDF documents to an existing envelope.
150
+ *
151
+ * Must be called after create() and before send().
152
+ *
153
+ * @param {string} envelopeId
154
+ * @param {DocumentUpload[]} documents
155
+ * @returns {Promise<UploadResult>}
156
+ *
157
+ * @example
158
+ * await client.envelopes.uploadDocuments('env_abc123', [
159
+ * { data: fs.readFileSync('./report.pdf'), filename: 'Q1-report.pdf' }
160
+ * ]);
161
+ */
162
+ uploadDocuments(envelopeId, documents) {
163
+ if (!envelopeId) throw new Error('envelopes.uploadDocuments: envelopeId is required');
164
+ if (!documents?.length) throw new Error('envelopes.uploadDocuments: documents array is required');
165
+
166
+ const form = new FormData();
167
+ for (const doc of documents) {
168
+ const buf = doc.data;
169
+ const name = doc.filename || 'document.pdf';
170
+ if (Buffer.isBuffer(buf)) {
171
+ form.append('documents', buf, { filename: name, contentType: 'application/pdf' });
172
+ } else if (buf && typeof buf.pipe === 'function') {
173
+ form.append('documents', buf, { filename: name, contentType: 'application/pdf' });
174
+ } else {
175
+ throw new Error(`Document "${name}" must be a Buffer or ReadStream`);
176
+ }
177
+ }
178
+
179
+ return this._http.post(`/sdk/v1/envelopes/${encodeURIComponent(envelopeId)}/documents`, {
180
+ formData: form
181
+ });
182
+ }
183
+
184
+ // ── Send ───────────────────────────────────────────────────────────────────
185
+
186
+ /**
187
+ * Transition an envelope to 'sent' status, making it ready for signing.
188
+ *
189
+ * @param {string} envelopeId
190
+ * @returns {Promise<EnvelopeResult>}
191
+ */
192
+ send(envelopeId) {
193
+ if (!envelopeId) throw new Error('envelopes.send: envelopeId is required');
194
+ return this._http.post(`/sdk/v1/envelopes/${encodeURIComponent(envelopeId)}/send`);
195
+ }
196
+
197
+ // ── Sign ───────────────────────────────────────────────────────────────────
198
+
199
+ /**
200
+ * Apply a cryptographic signature to all documents in the envelope.
201
+ *
202
+ * This is the core signing step. For SDK requests, interactive 2FA is
203
+ * bypassed — the API key alone authorises the signature.
204
+ *
205
+ * The resulting signatures are PAdES-LTV compliant (PDF Advanced Electronic
206
+ * Signatures with Long-Term Validation).
207
+ *
208
+ * @param {string} envelopeId
209
+ * @param {SignEnvelopeOptions} [options]
210
+ * @returns {Promise<SignResult>}
211
+ *
212
+ * @example
213
+ * const result = await client.envelopes.sign('env_abc123', {
214
+ * reason: 'Claims approval — NHIF Kenya',
215
+ * location: 'Nairobi, Kenya'
216
+ * });
217
+ * console.log(result.data.signatures[0].certificate.serialNumber);
218
+ */
219
+ sign(envelopeId, options = {}) {
220
+ if (!envelopeId) throw new Error('envelopes.sign: envelopeId is required');
221
+ const { reason = 'Digital signature', location = 'Nairobi, Kenya' } = options;
222
+ return this._http.post(`/sdk/v1/envelopes/${encodeURIComponent(envelopeId)}/sign`, {
223
+ data: { reason, location }
224
+ });
225
+ }
226
+
227
+ // ── Download Document ──────────────────────────────────────────────────────
228
+
229
+ /**
230
+ * Download a signed document as a Buffer.
231
+ *
232
+ * @param {string} envelopeId
233
+ * @param {string} documentId
234
+ * @returns {Promise<Buffer>} Raw PDF bytes
235
+ *
236
+ * @example
237
+ * const pdfBuffer = await client.envelopes.getDocument('env_abc123', 'doc_xyz789');
238
+ * fs.writeFileSync('./signed-claim.pdf', pdfBuffer);
239
+ */
240
+ async getDocument(envelopeId, documentId) {
241
+ if (!envelopeId) throw new Error('envelopes.getDocument: envelopeId is required');
242
+ if (!documentId) throw new Error('envelopes.getDocument: documentId is required');
243
+
244
+ const response = await this._http.get(
245
+ `/sdk/v1/envelopes/${encodeURIComponent(envelopeId)}/documents/${encodeURIComponent(documentId)}`,
246
+ { responseType: 'arraybuffer' }
247
+ );
248
+ return Buffer.isBuffer(response) ? response : Buffer.from(response);
249
+ }
250
+
251
+ // ── Audit Trail ────────────────────────────────────────────────────────────
252
+
253
+ /**
254
+ * Retrieve the cryptographically verifiable audit trail for an envelope.
255
+ *
256
+ * The audit chain is a Merkle-linked log of all envelope events:
257
+ * - created, document_uploaded, sent, signed, certificate_issued, completed
258
+ *
259
+ * Each entry includes a SHA-256 event hash and chain hash linking it to
260
+ * the previous entry, making the log tamper-evident.
261
+ *
262
+ * @param {string} envelopeId
263
+ * @returns {Promise<AuditTrailResult>}
264
+ *
265
+ * @example
266
+ * const { data } = await client.envelopes.getAuditTrail('env_abc123');
267
+ * data.auditTrail.forEach(event => {
268
+ * console.log(event.timestamp, event.action, event.eventHash);
269
+ * });
270
+ * if (data.chainIntegrity.valid) console.log('Audit chain intact');
271
+ */
272
+ getAuditTrail(envelopeId) {
273
+ if (!envelopeId) throw new Error('envelopes.getAuditTrail: envelopeId is required');
274
+ return this._http.get(`/sdk/v1/envelopes/${encodeURIComponent(envelopeId)}/audit`);
275
+ }
276
+ }
277
+
278
+ /**
279
+ * @typedef {Object} CreateEnvelopeOptions
280
+ * @property {string} title - Envelope title
281
+ * @property {{ name: string, email?: string, role?: string }[]} [signers]
282
+ * @property {string} [message] - Message to signers
283
+ * @property {Object} [metadata]
284
+ */
285
+
286
+ /**
287
+ * @typedef {Object} DocumentUpload
288
+ * @property {Buffer|import('fs').ReadStream} data
289
+ * @property {string} [filename]
290
+ */
291
+
292
+ /**
293
+ * @typedef {Object} SignEnvelopeOptions
294
+ * @property {string} [reason]
295
+ * @property {string} [location]
296
+ */
297
+
298
+ /**
299
+ * @typedef {Object} ListEnvelopesOptions
300
+ * @property {'draft'|'sent'|'in_progress'|'completed'|'cancelled'} [status]
301
+ * @property {number} [page]
302
+ * @property {number} [limit]
303
+ * @property {string} [search]
304
+ */
305
+
306
+ module.exports = { EnvelopeResource };