@signingstudio/sdk 1.0.0
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/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +239 -0
- package/dist/index.cjs +696 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +702 -0
- package/dist/index.d.ts +702 -0
- package/dist/index.js +660 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@signingstudio/sdk` are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [1.0.0] — 2026-07-20
|
|
8
|
+
|
|
9
|
+
Initial public release. Covers 100% of the Signing Studio v1 REST API.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `SigningStudio` client with `documents` and `templates` resources.
|
|
13
|
+
- Full TypeScript typings for every request and response shape.
|
|
14
|
+
- `Documents` resource: list, send, get, delete (soft + hard), restore, archive, unarchive, cancel, progress, download URL, download PDF, activity, remind.
|
|
15
|
+
- `Templates` resource: list, create (PDF upload), get, update, delete, PDF URL, download PDF, replace PDF, set fields, duplicate, archive, history, history-PDF download.
|
|
16
|
+
- `verifyWebhook` helper — constant-time HMAC-SHA256 signature check.
|
|
17
|
+
- Typed error hierarchy: `SigningStudioError` → `ApiError` → `AuthenticationError`, `NotFoundError`, `ValidationError`, `RateLimitError`.
|
|
18
|
+
- Bounded retry policy — 429 (with Retry-After ≤ 60s), 5xx, and transport errors are retried with exponential backoff.
|
|
19
|
+
- Dual ESM + CJS build with source maps.
|
|
20
|
+
- Vitest test suite covering the HTTP transport, resources, and webhook verifier.
|
|
21
|
+
- GitHub Actions CI on Node 18 / 20 / 22, publish workflow with npm provenance.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Signing Studio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Signing Studio Node.js SDK
|
|
2
|
+
|
|
3
|
+
[](https://github.com/alyasdds/signingstudio-node/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@signingstudio/sdk)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
Official Node.js / TypeScript client for the **[Signing Studio](https://signingstudio.com)** e-signature API. Covers every public v1 endpoint — send documents from templates, poll signing progress, manage templates and their fields, and verify webhook deliveries.
|
|
8
|
+
|
|
9
|
+
Ships as ESM + CJS with full TypeScript typings. Node **18+**.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @signingstudio/sdk
|
|
15
|
+
pnpm add @signingstudio/sdk
|
|
16
|
+
yarn add @signingstudio/sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { SigningStudio } from "@signingstudio/sdk";
|
|
23
|
+
|
|
24
|
+
const client = new SigningStudio({ apiKey: process.env.SIGNING_STUDIO_API_KEY! });
|
|
25
|
+
|
|
26
|
+
const doc = await client.documents.send({
|
|
27
|
+
template_id: "11111111-2222-3333-4444-555555555555",
|
|
28
|
+
title: "MSA — Acme",
|
|
29
|
+
recipients: [{ name: "Alex Doe", email: "alex@acme.com" }],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log(`Sent ${doc.id} (${doc.status})`);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Get your API key from **Signing Studio → Settings → API Keys**. The plaintext key is shown once at creation — store it in a secrets manager.
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
new SigningStudio({
|
|
41
|
+
apiKey: "sk_live_...",
|
|
42
|
+
baseUrl: "https://api.signingstudio.com", // default
|
|
43
|
+
maxRetries: 3, // 429 + 5xx + network
|
|
44
|
+
requestTimeoutMs: 120_000, // per request
|
|
45
|
+
userAgent: "my-app/1.4",
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Retry policy** — conservative and predictable:
|
|
50
|
+
- **429** with `Retry-After ≤ 60s` → sleep and retry, up to `maxRetries`.
|
|
51
|
+
- **429** with `Retry-After > 60s` (typically monthly quota) → throw `RateLimitError` immediately.
|
|
52
|
+
- **5xx** and **network errors** → exponential backoff (500ms · 1s · 2s · 4s) with jitter.
|
|
53
|
+
|
|
54
|
+
## Documents
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// List
|
|
58
|
+
const list = await client.documents.list({ status: "sent", view: "active", limit: 50 });
|
|
59
|
+
|
|
60
|
+
// Send from a template
|
|
61
|
+
const doc = await client.documents.send({
|
|
62
|
+
template_id,
|
|
63
|
+
title: "MSA — Acme",
|
|
64
|
+
subject: "Please sign",
|
|
65
|
+
message: "Signing at your convenience",
|
|
66
|
+
expires_at: "2026-08-01T00:00:00Z",
|
|
67
|
+
recipients: [
|
|
68
|
+
{ name: "Alex", email: "alex@acme.com", signing_order: 0 },
|
|
69
|
+
{ name: "Bo", email: "bo@acme.com", signing_order: 1 },
|
|
70
|
+
],
|
|
71
|
+
prefill_values: [{ field_name: "company", value: "Acme Inc." }],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Read
|
|
75
|
+
await client.documents.get(id);
|
|
76
|
+
await client.documents.progress(id); // cheap; ideal for polling
|
|
77
|
+
await client.documents.activity(id);
|
|
78
|
+
|
|
79
|
+
// Actions
|
|
80
|
+
await client.documents.cancel(id);
|
|
81
|
+
await client.documents.archive(id);
|
|
82
|
+
await client.documents.unarchive(id);
|
|
83
|
+
await client.documents.restore(id);
|
|
84
|
+
await client.documents.delete(id); // soft
|
|
85
|
+
await client.documents.delete(id, { hard: true });
|
|
86
|
+
|
|
87
|
+
// Reminder — mints a fresh signing URL for a specific recipient
|
|
88
|
+
const { signing_url } = await client.documents.remind(documentId, recipientId);
|
|
89
|
+
|
|
90
|
+
// Download
|
|
91
|
+
const { url } = await client.documents.downloadUrl(id); // presigned URL
|
|
92
|
+
const bytes = await client.documents.downloadPdf(id); // Uint8Array
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Send-payload rules the server enforces
|
|
96
|
+
|
|
97
|
+
- `template_id` is required; ad-hoc PDF sends without a template are not exposed on v1.
|
|
98
|
+
- `recipients` must have at least one entry. Sequential signing runs in `signing_order`.
|
|
99
|
+
- `prefill_values[]` must have either `field_id` (uuid) or a non-empty `field_name` slug plus a `value`.
|
|
100
|
+
- Signature / initials fields cannot be prefilled — 422 if attempted.
|
|
101
|
+
- Any template field that is both `required` and `readonly` MUST be prefilled — 422 with a list of missing labels.
|
|
102
|
+
- Sends count against the account's monthly `documents_per_month` quota — 429 when tripped.
|
|
103
|
+
|
|
104
|
+
## Templates
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
// List active templates
|
|
108
|
+
const templates = await client.templates.list();
|
|
109
|
+
|
|
110
|
+
// Create from a PDF — path, Buffer/Uint8Array, or Blob all accepted
|
|
111
|
+
const template = await client.templates.create("./msa.pdf", {
|
|
112
|
+
name: "MSA v2",
|
|
113
|
+
signer_count: 1,
|
|
114
|
+
delivery_methods: ["email"],
|
|
115
|
+
signers: [{ role: "Customer", delivery: ["email"] }],
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Update / delete
|
|
119
|
+
await client.templates.update(template.id, { name: "MSA v3" });
|
|
120
|
+
await client.templates.delete(template.id);
|
|
121
|
+
|
|
122
|
+
// PDF versioning
|
|
123
|
+
await client.templates.replacePdf(template.id, "./msa-updated.pdf");
|
|
124
|
+
const versions = await client.templates.history(template.id);
|
|
125
|
+
const oldPdf = await client.templates.downloadHistoryPdf(template.id, versions[0].id);
|
|
126
|
+
|
|
127
|
+
// Fields — full replace
|
|
128
|
+
await client.templates.setFields(template.id, [
|
|
129
|
+
{ field_type: "signature", page: 1, x: 60, y: 82, width: 30, height: 6, signer_index: 0, required: true },
|
|
130
|
+
{ field_type: "text", page: 1, x: 10, y: 20, width: 30, height: 4, signer_index: 0, name: "company", label: "Company name", required: true },
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
// Duplicate + archive
|
|
134
|
+
const copy = await client.templates.duplicate(template.id);
|
|
135
|
+
await client.templates.archive(template.id);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### PDF upload constraints
|
|
139
|
+
|
|
140
|
+
- Max **50 MB** per file.
|
|
141
|
+
- `application/pdf` only — other content types 400.
|
|
142
|
+
- The multipart form field name must be `file` — the SDK sets this for you.
|
|
143
|
+
|
|
144
|
+
## Webhooks
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import express from "express";
|
|
148
|
+
import { verifyWebhook, type WebhookPayload } from "@signingstudio/sdk";
|
|
149
|
+
|
|
150
|
+
const app = express();
|
|
151
|
+
|
|
152
|
+
app.post(
|
|
153
|
+
"/webhooks/signing-studio",
|
|
154
|
+
express.raw({ type: "application/json" }), // do NOT pre-parse
|
|
155
|
+
(req, res) => {
|
|
156
|
+
if (!verifyWebhook(req.body, req.header("X-DDS-Signature") ?? null, process.env.WEBHOOK_SECRET!)) {
|
|
157
|
+
return res.status(401).end();
|
|
158
|
+
}
|
|
159
|
+
const payload = JSON.parse(req.body.toString("utf8")) as WebhookPayload;
|
|
160
|
+
// payload.event is one of:
|
|
161
|
+
// "document.sent" | "document.viewed" | "document.signed" | "document.declined" | "document.completed"
|
|
162
|
+
res.status(200).end();
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Always sign the RAW body**, not a parsed-and-re-encoded body. Any whitespace shift breaks the HMAC.
|
|
168
|
+
|
|
169
|
+
### Payload shape
|
|
170
|
+
|
|
171
|
+
See [types.ts](src/types.ts) — `WebhookPayload` is fully typed.
|
|
172
|
+
|
|
173
|
+
## Errors
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import {
|
|
177
|
+
SigningStudioError,
|
|
178
|
+
ApiError,
|
|
179
|
+
AuthenticationError,
|
|
180
|
+
NotFoundError,
|
|
181
|
+
ValidationError,
|
|
182
|
+
RateLimitError,
|
|
183
|
+
} from "@signingstudio/sdk";
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
await client.documents.send(payload);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
if (err instanceof ValidationError) {
|
|
189
|
+
// err.errors is Record<string, string[]>
|
|
190
|
+
} else if (err instanceof RateLimitError) {
|
|
191
|
+
// err.window === "minute" | "day" | null
|
|
192
|
+
// err.retryAfter === number | null
|
|
193
|
+
} else if (err instanceof AuthenticationError) {
|
|
194
|
+
// Refresh the API key.
|
|
195
|
+
} else if (err instanceof NotFoundError) {
|
|
196
|
+
// Doesn't exist on this tenant.
|
|
197
|
+
} else if (err instanceof ApiError) {
|
|
198
|
+
console.error(`request=${err.requestId} status=${err.statusCode}`);
|
|
199
|
+
}
|
|
200
|
+
throw err;
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
All SDK-thrown exceptions inherit from `SigningStudioError`.
|
|
205
|
+
|
|
206
|
+
## Rate limits
|
|
207
|
+
|
|
208
|
+
Every response carries:
|
|
209
|
+
|
|
210
|
+
- `X-RateLimit-Limit-Minute`, `X-RateLimit-Remaining-Minute`
|
|
211
|
+
- `X-RateLimit-Limit-Day`, `X-RateLimit-Remaining-Day`
|
|
212
|
+
|
|
213
|
+
Access them via the low-level HTTP layer if you need to display or throttle:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
const response = await client.http.requestJson({ method: "GET", path: "documents" });
|
|
217
|
+
console.log(response.rateLimit.remainingMinute);
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Platform defaults: **120 req/min** and **20,000 req/day** per API key.
|
|
221
|
+
|
|
222
|
+
## Testing
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
npm ci
|
|
226
|
+
npm run typecheck
|
|
227
|
+
npm run test
|
|
228
|
+
npm run build
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
CI runs against Node 18, 20, 22 on every push/PR.
|
|
232
|
+
|
|
233
|
+
## Versioning
|
|
234
|
+
|
|
235
|
+
Semantic versioning. `CHANGELOG.md` records every release.
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
MIT. See [LICENSE](LICENSE).
|