@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 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
+ [![CI](https://github.com/alyasdds/signingstudio-node/actions/workflows/ci.yml/badge.svg)](https://github.com/alyasdds/signingstudio-node/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/@signingstudio/sdk.svg)](https://www.npmjs.com/package/@signingstudio/sdk)
5
+ [![License](https://img.shields.io/npm/l/@signingstudio/sdk.svg)](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).