helprange 0.0.0-stage → 0.1.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 +32 -0
- package/LICENSE +21 -0
- package/README.md +353 -2
- package/dist/cjs/client.d.ts +94 -0
- package/dist/cjs/client.js +274 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/core.d.ts +33 -0
- package/dist/cjs/core.js +25 -0
- package/dist/cjs/core.js.map +1 -0
- package/dist/cjs/errors.d.ts +81 -0
- package/dist/cjs/errors.js +145 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/generated/resources.d.ts +800 -0
- package/dist/cjs/generated/resources.js +1013 -0
- package/dist/cjs/generated/resources.js.map +1 -0
- package/dist/cjs/generated/spec-version.d.ts +1 -0
- package/dist/cjs/generated/spec-version.js +6 -0
- package/dist/cjs/generated/spec-version.js.map +1 -0
- package/dist/cjs/generated/types.d.ts +1667 -0
- package/dist/cjs/generated/types.js +5 -0
- package/dist/cjs/generated/types.js.map +1 -0
- package/dist/cjs/index.d.ts +12 -0
- package/dist/cjs/index.js +38 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/version.d.ts +2 -0
- package/dist/cjs/version.js +6 -0
- package/dist/cjs/version.js.map +1 -0
- package/dist/cjs/webhooks.d.ts +37 -0
- package/dist/cjs/webhooks.js +94 -0
- package/dist/cjs/webhooks.js.map +1 -0
- package/dist/esm/client.d.ts +94 -0
- package/dist/esm/client.js +269 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/core.d.ts +33 -0
- package/dist/esm/core.js +21 -0
- package/dist/esm/core.js.map +1 -0
- package/dist/esm/errors.d.ts +81 -0
- package/dist/esm/errors.js +124 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/generated/resources.d.ts +800 -0
- package/dist/esm/generated/resources.js +989 -0
- package/dist/esm/generated/resources.js.map +1 -0
- package/dist/esm/generated/spec-version.d.ts +1 -0
- package/dist/esm/generated/spec-version.js +3 -0
- package/dist/esm/generated/spec-version.js.map +1 -0
- package/dist/esm/generated/types.d.ts +1667 -0
- package/dist/esm/generated/types.js +4 -0
- package/dist/esm/generated/types.js.map +1 -0
- package/dist/esm/index.d.ts +12 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/version.d.ts +2 -0
- package/dist/esm/version.js +3 -0
- package/dist/esm/version.js.map +1 -0
- package/dist/esm/webhooks.d.ts +37 -0
- package/dist/esm/webhooks.js +88 -0
- package/dist/esm/webhooks.js.map +1 -0
- package/package.json +51 -4
- package/src/client.ts +315 -0
- package/src/core.ts +49 -0
- package/src/errors.ts +107 -0
- package/src/generated/resources.ts +1081 -0
- package/src/generated/spec-version.ts +2 -0
- package/src/generated/types.ts +1844 -0
- package/src/index.ts +12 -0
- package/src/version.ts +2 -0
- package/src/webhooks.ts +105 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this package are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the package uses [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.1.0] – unreleased
|
|
9
|
+
|
|
10
|
+
First release, generated from HelpRange API spec version 2026-11-15 (not published yet; this entry describes
|
|
11
|
+
everything that will ship in it).
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Client with every REST API v2 endpoint, grouped by tag: account, folders, documents, links, triggers, data rooms,
|
|
16
|
+
NDAs, e-signature, offers, store, lead forms, activity, webhook endpoints.
|
|
17
|
+
- Data rooms end to end: `dataRooms.create` (with folders and NDA settings), `update`, `delete({ confirm: true })`;
|
|
18
|
+
sub-resources `dataRooms.guests` (`list`, `invite`, `remove`), `dataRooms.documents` (`list`, `add`, `remove`,
|
|
19
|
+
`move`), `dataRooms.folders`, `dataRooms.questions`, `dataRooms.requestItems`, `dataRooms.accessRequests`,
|
|
20
|
+
`dataRooms.signatureRequests`; `ndas.list`.
|
|
21
|
+
- `links.list` (every share link of the team, with filters and pagination) and `links.views` (the views of one link).
|
|
22
|
+
- `webhookEndpoints`: `list`, `create`, `get`, `update`, `delete`, `rotateSecret`, `sendTest`, `deliveries`,
|
|
23
|
+
`redeliver`.
|
|
24
|
+
- Typed request and response models for every endpoint.
|
|
25
|
+
- `share()` – upload a file and create a share link in one call.
|
|
26
|
+
- Automatic `Idempotency-Key` (UUID v4) on every POST / PATCH, reused across retries; `options.idempotencyKey` to set
|
|
27
|
+
your own.
|
|
28
|
+
- Retries with backoff on 429 (any method, honouring Retry-After), on 408/5xx/connection errors/timeouts (GET and
|
|
29
|
+
keyed POST / PATCH) and on 409 `idempotency_key_in_progress` (up to 5 extra attempts).
|
|
30
|
+
- Typed errors with status, code and body (incl. `IdempotencyKeyInProgressError`, `UnprocessableEntityError`,
|
|
31
|
+
`IdempotencyKeyReusedError`); test-mode (`hr_test_`) detection; rate-limit headers after each response.
|
|
32
|
+
- Webhook signature verification (`HelpRange-Signature`, v1 scheme, rotation-aware) and a signing helper for tests.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HelpRange
|
|
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
CHANGED
|
@@ -1,3 +1,354 @@
|
|
|
1
|
-
#
|
|
1
|
+
# HelpRange Node.js / TypeScript SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official client for the [HelpRange API](https://www.helprange.com/developers): upload documents, create tracked share
|
|
4
|
+
links, see who viewed what (page by page, link by link), create and run data rooms (folders, NDA, guests, Q&A,
|
|
5
|
+
request lists), send documents for e-signature, create offers and manage your webhook endpoint.
|
|
6
|
+
|
|
7
|
+
- TypeScript types for every request and response, generated from the [OpenAPI spec](https://www.helprange.com/developers/openapi.json)
|
|
8
|
+
- ESM and CommonJS builds, **zero runtime dependencies** (uses the global `fetch` of Node 18+)
|
|
9
|
+
- Automatic `Idempotency-Key` on every POST / PATCH, so retries on 429 / 5xx / network errors are safe for writes too
|
|
10
|
+
- Typed errors, test-mode detection, webhook signature verification
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install helprange
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Node.js 18 or newer.
|
|
19
|
+
|
|
20
|
+
## Quickstart
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { readFile } from 'node:fs/promises';
|
|
24
|
+
import { HelpRange } from 'helprange';
|
|
25
|
+
|
|
26
|
+
const hr = new HelpRange({ apiKey: process.env.HELPRANGE_API_KEY });
|
|
27
|
+
|
|
28
|
+
// Upload a PDF and create a tracked share link in one call
|
|
29
|
+
const { document, link } = await hr.share({
|
|
30
|
+
file: await readFile('proposal.pdf'),
|
|
31
|
+
filename: 'proposal.pdf',
|
|
32
|
+
link: { label: 'ACME - Anna', expires_on: '2026-12-31', allow_download: false, watermark: true },
|
|
33
|
+
});
|
|
34
|
+
console.log(link.url); // https://www.helprange.com/view/…/proposal.pdf
|
|
35
|
+
|
|
36
|
+
// Who opened it, when and for how long
|
|
37
|
+
const stats = await hr.documents.get(document.id, { recent_views: 20 });
|
|
38
|
+
for (const v of stats.recent_views) console.log(v.viewer, v.opened_at, v.seconds_read);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
CommonJS works too: `const { HelpRange } = require('helprange');`
|
|
42
|
+
|
|
43
|
+
## Authentication
|
|
44
|
+
|
|
45
|
+
Create a team API key in HelpRange → **Settings → API keys**. It acts as the administrator who created it, limited to
|
|
46
|
+
its **scopes** (`documents:read`, `documents:write`, `links:read`, `links:write`, `rooms:read`, `rooms:write`,
|
|
47
|
+
`analytics:read`, `team:read`). Every method's JSDoc names the scope it needs.
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
const hr = new HelpRange({ apiKey: 'hr_live_…' }); // or set HELPRANGE_API_KEY and call new HelpRange()
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The SDK sends `Authorization: Bearer <key>`. Keep keys on your server – never ship them to a browser.
|
|
54
|
+
|
|
55
|
+
The REST API is part of the Max and Unlimited plans, the Deal Room Pass and the trial; other plans get a
|
|
56
|
+
`PaymentRequiredError` (402).
|
|
57
|
+
|
|
58
|
+
## Sandbox (test mode)
|
|
59
|
+
|
|
60
|
+
Keys starting with `hr_test_` work on a separate sandbox of your team with sample data – never on live data:
|
|
61
|
+
|
|
62
|
+
- no e-mail is sent (only addresses at reserved test domains such as `example.com` are accepted; others fail with
|
|
63
|
+
`BadRequestError` code `sandbox_email_domain`), nothing reaches Stripe, sandbox activity sends no webhooks;
|
|
64
|
+
- every JSON response carries `livemode: false`;
|
|
65
|
+
- sandbox data is deleted 30 days after the sandbox was created or reset (Settings → API keys → Reset sandbox).
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const hr = new HelpRange({ apiKey: process.env.HELPRANGE_TEST_KEY });
|
|
69
|
+
hr.testMode; // true for hr_test_ keys
|
|
70
|
+
await hr.account.me();
|
|
71
|
+
hr.rateLimit.livemode; // false – from the X-HelpRange-Livemode header
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Resources
|
|
75
|
+
|
|
76
|
+
Resources follow the tags of the [API reference](https://www.helprange.com/developers/api):
|
|
77
|
+
|
|
78
|
+
| Property | What |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `hr.account` | Who is connected, the plan and its limits |
|
|
81
|
+
| `hr.folders` | Folders and subfolders |
|
|
82
|
+
| `hr.documents` | Uploads, documents, statistics and share links |
|
|
83
|
+
| `hr.links` | Every share link of the team (filters, pagination) and the views of one link |
|
|
84
|
+
| `hr.triggers` | Newest views and uploads (polling, e.g. for Zapier / Make / n8n) |
|
|
85
|
+
| `hr.dataRooms` | Create, change and delete data rooms; the index. Sub-resources: `.guests`, `.documents`, `.folders`, `.questions`, `.requestItems`, `.accessRequests`, `.signatureRequests` |
|
|
86
|
+
| `hr.ndas` | The team's NDAs (for a data room's `nda_id`) and whether the plan has NDAs |
|
|
87
|
+
| `hr.signatureRequests` | E-signature requests and signed PDFs |
|
|
88
|
+
| `hr.offers` | Offers with their own link |
|
|
89
|
+
| `hr.store` | Store sales and buyer access |
|
|
90
|
+
| `hr.leadForms` | Lead-magnet / landing-page templates |
|
|
91
|
+
| `hr.activity` | The team activity feed |
|
|
92
|
+
| `hr.webhookEndpoints` | The team's webhook endpoint: create, change, delete, rotate the secret, test, delivery log, redeliver |
|
|
93
|
+
|
|
94
|
+
Request bodies and query parameters use the API's field names (`snake_case`), exactly as in the reference. Path
|
|
95
|
+
parameters are positional: `hr.documents.createLink(fileId, body)`.
|
|
96
|
+
|
|
97
|
+
Every method takes optional `RequestOptions` last: `{ signal, timeout, maxRetries, headers, idempotencyKey }`.
|
|
98
|
+
For an endpoint this version does not wrap yet, use `hr.request({ method: 'GET', path: '/api/v2/…' })`.
|
|
99
|
+
|
|
100
|
+
### All endpoints
|
|
101
|
+
|
|
102
|
+
<!-- BEGIN GENERATED ENDPOINTS -->
|
|
103
|
+
| Method | Endpoint | Scope |
|
|
104
|
+
| --- | --- | --- |
|
|
105
|
+
| `hr.account.me()` | `GET /api/v2/me` | `team:read` |
|
|
106
|
+
| `hr.account.plan()` | `GET /api/v2/plan` | `team:read` |
|
|
107
|
+
| `hr.folders.list()` | `GET /api/v2/folders` | `documents:read` |
|
|
108
|
+
| `hr.folders.get(folderId)` | `GET /api/v2/folders/{folder_id}` | `documents:read` |
|
|
109
|
+
| `hr.folders.setSubfoldersEnabled(folderId, body)` | `POST /api/v2/folders/{folder_id}` | `documents:write` |
|
|
110
|
+
| `hr.folders.listSubfolders(folderId)` | `GET /api/v2/folders/{folder_id}/subfolders` | `documents:read` |
|
|
111
|
+
| `hr.folders.createSubfolder(folderId, body)` | `POST /api/v2/folders/{folder_id}/subfolders` | `documents:write` |
|
|
112
|
+
| `hr.folders.moveDocuments(folderId, body)` | `POST /api/v2/folders/{folder_id}/subfolders/move` | `documents:write` |
|
|
113
|
+
| `hr.folders.renameSubfolder(subfolderId, body)` | `POST /api/v2/subfolders/{subfolder_id}` | `documents:write` |
|
|
114
|
+
| `hr.folders.deleteSubfolder(subfolderId)` | `POST /api/v2/subfolders/{subfolder_id}/delete` | `documents:write` |
|
|
115
|
+
| `hr.documents.list(query)` | `GET /api/v2/documents` | `documents:read` |
|
|
116
|
+
| `hr.documents.upload(body)` | `POST /api/v2/files` | `documents:write` |
|
|
117
|
+
| `hr.documents.createLink(fileId, body)` | `POST /api/v2/files/{file_id}/links` | `links:write` |
|
|
118
|
+
| `hr.documents.get(fileId, query)` | `GET /api/v2/documents/{file_id}` | `documents:read` |
|
|
119
|
+
| `hr.documents.listLinks(fileId)` | `GET /api/v2/documents/{file_id}/links` | `links:read` |
|
|
120
|
+
| `hr.links.list(query)` | `GET /api/v2/links` | `links:read` |
|
|
121
|
+
| `hr.links.views(linkId, query)` | `GET /api/v2/links/{link_id}/views` | `analytics:read` |
|
|
122
|
+
| `hr.triggers.recentViews()` | `GET /api/v2/views` | `analytics:read` |
|
|
123
|
+
| `hr.triggers.recentUploads()` | `GET /api/v2/uploads` | `documents:read` |
|
|
124
|
+
| `hr.store.grantBuyerAccess(folderId, body)` | `POST /api/v2/folders/{folder_id}/buyers` | `links:write` |
|
|
125
|
+
| `hr.store.revokeBuyerAccess(folderId, body)` | `POST /api/v2/folders/{folder_id}/buyers/remove` | `links:write` |
|
|
126
|
+
| `hr.store.listSales(query)` | `GET /api/v2/store/sales` | `analytics:read` |
|
|
127
|
+
| `hr.leadForms.listTemplates()` | `GET /api/v2/landing-templates` | `links:read` |
|
|
128
|
+
| `hr.activity.list(query)` | `GET /api/v2/activity` | `analytics:read` |
|
|
129
|
+
| `hr.signatureRequests.list(query)` | `GET /api/v2/signature-requests` | `documents:read` |
|
|
130
|
+
| `hr.signatureRequests.create(body)` | `POST /api/v2/signature-requests` | `documents:write` |
|
|
131
|
+
| `hr.signatureRequests.get(envelopeId)` | `GET /api/v2/signature-requests/{envelope_id}` | `documents:read` |
|
|
132
|
+
| `hr.signatureRequests.remind(envelopeId)` | `POST /api/v2/signature-requests/{envelope_id}/remind` | `documents:write` |
|
|
133
|
+
| `hr.signatureRequests.void(envelopeId, body)` | `POST /api/v2/signature-requests/{envelope_id}/void` | `documents:write` |
|
|
134
|
+
| `hr.signatureRequests.getSignedPdf(envelopeId)` | `GET /api/v2/signature-requests/{envelope_id}/signed-pdf` | `documents:read` |
|
|
135
|
+
| `hr.dataRooms.list()` | `GET /api/v2/data-rooms` | `rooms:read` |
|
|
136
|
+
| `hr.dataRooms.create(body)` | `POST /api/v2/data-rooms` | `rooms:write` |
|
|
137
|
+
| `hr.dataRooms.get(dataRoomId)` | `GET /api/v2/data-rooms/{data_room_id}` | `rooms:read` |
|
|
138
|
+
| `hr.dataRooms.update(dataRoomId, body)` | `POST /api/v2/data-rooms/{data_room_id}` | `rooms:write` |
|
|
139
|
+
| `hr.dataRooms.delete(dataRoomId, body)` | `POST /api/v2/data-rooms/{data_room_id}/delete` | `rooms:write` |
|
|
140
|
+
| `hr.ndas.list()` | `GET /api/v2/ndas` | `rooms:read` |
|
|
141
|
+
| `hr.dataRooms.getIndex(dataRoomId)` | `GET /api/v2/data-rooms/{data_room_id}/index` | `rooms:read` |
|
|
142
|
+
| `hr.dataRooms.guests.list(dataRoomId)` | `GET /api/v2/data-rooms/{data_room_id}/guests` | `rooms:read` |
|
|
143
|
+
| `hr.dataRooms.guests.invite(dataRoomId, body)` | `POST /api/v2/data-rooms/{data_room_id}/guests` | `rooms:write` |
|
|
144
|
+
| `hr.dataRooms.guests.remove(dataRoomId, body)` | `POST /api/v2/data-rooms/{data_room_id}/guests/remove` | `rooms:write` |
|
|
145
|
+
| `hr.dataRooms.accessRequests.list(dataRoomId, query)` | `GET /api/v2/data-rooms/{data_room_id}/access-requests` | `rooms:read` |
|
|
146
|
+
| `hr.dataRooms.signatureRequests.list(dataRoomId)` | `GET /api/v2/data-rooms/{data_room_id}/signature-requests` | `rooms:read` |
|
|
147
|
+
| `hr.dataRooms.questions.list(dataRoomId, query)` | `GET /api/v2/data-rooms/{data_room_id}/questions` | `rooms:read` |
|
|
148
|
+
| `hr.dataRooms.questions.add(dataRoomId, body)` | `POST /api/v2/data-rooms/{data_room_id}/questions` | `rooms:write` |
|
|
149
|
+
| `hr.dataRooms.requestItems.list(dataRoomId, query)` | `GET /api/v2/data-rooms/{data_room_id}/requests` | `rooms:read` |
|
|
150
|
+
| `hr.dataRooms.requestItems.add(dataRoomId, body)` | `POST /api/v2/data-rooms/{data_room_id}/requests` | `rooms:write` |
|
|
151
|
+
| `hr.dataRooms.folders.list(dataRoomId)` | `GET /api/v2/data-rooms/{data_room_id}/folders` | `rooms:read` |
|
|
152
|
+
| `hr.dataRooms.folders.create(dataRoomId, body)` | `POST /api/v2/data-rooms/{data_room_id}/folders` | `rooms:write` |
|
|
153
|
+
| `hr.dataRooms.documents.move(dataRoomId, body)` | `POST /api/v2/data-rooms/{data_room_id}/folders/move` | `rooms:write` |
|
|
154
|
+
| `hr.dataRooms.folders.rename(dataRoomId, folderId, body)` | `POST /api/v2/data-rooms/{data_room_id}/folders/{folder_id}` | `rooms:write` |
|
|
155
|
+
| `hr.dataRooms.folders.delete(dataRoomId, folderId)` | `POST /api/v2/data-rooms/{data_room_id}/folders/{folder_id}/delete` | `rooms:write` |
|
|
156
|
+
| `hr.dataRooms.documents.list(dataRoomId)` | `GET /api/v2/data-rooms/{data_room_id}/documents` | `rooms:read` |
|
|
157
|
+
| `hr.dataRooms.documents.add(dataRoomId, body)` | `POST /api/v2/data-rooms/{data_room_id}/documents` | `rooms:write` |
|
|
158
|
+
| `hr.dataRooms.documents.remove(dataRoomId, body)` | `POST /api/v2/data-rooms/{data_room_id}/documents/remove` | `rooms:write` |
|
|
159
|
+
| `hr.offers.list(query)` | `GET /api/v2/offers` | `links:read` |
|
|
160
|
+
| `hr.offers.create(body)` | `POST /api/v2/offers` | `links:write` |
|
|
161
|
+
| `hr.offers.get(offerId)` | `GET /api/v2/offers/{offer_id}` | `links:read` |
|
|
162
|
+
| `hr.offers.update(offerId, body)` | `PATCH /api/v2/offers/{offer_id}` | `links:write` |
|
|
163
|
+
| `hr.offers.addFiles(offerId, body)` | `POST /api/v2/offers/{offer_id}/files` | `links:write` |
|
|
164
|
+
| `hr.offers.removeFiles(offerId, body)` | `POST /api/v2/offers/{offer_id}/files/remove` | `links:write` |
|
|
165
|
+
| `hr.offers.listResponses(offerId, query)` | `GET /api/v2/offers/{offer_id}/responses` | `analytics:read` |
|
|
166
|
+
| `hr.offers.listAllResponses(query)` | `GET /api/v2/offer-responses` | `analytics:read` |
|
|
167
|
+
| `hr.webhookEndpoints.list()` | `GET /api/v2/webhook-endpoints` | `webhooks:manage` |
|
|
168
|
+
| `hr.webhookEndpoints.create(body)` | `POST /api/v2/webhook-endpoints` | `webhooks:manage` |
|
|
169
|
+
| `hr.webhookEndpoints.get(endpointId)` | `GET /api/v2/webhook-endpoints/{endpoint_id}` | `webhooks:manage` |
|
|
170
|
+
| `hr.webhookEndpoints.update(endpointId, body)` | `POST /api/v2/webhook-endpoints/{endpoint_id}` | `webhooks:manage` |
|
|
171
|
+
| `hr.webhookEndpoints.delete(endpointId)` | `POST /api/v2/webhook-endpoints/{endpoint_id}/delete` | `webhooks:manage` |
|
|
172
|
+
| `hr.webhookEndpoints.rotateSecret(endpointId)` | `POST /api/v2/webhook-endpoints/{endpoint_id}/rotate-secret` | `webhooks:manage` |
|
|
173
|
+
| `hr.webhookEndpoints.sendTest(endpointId)` | `POST /api/v2/webhook-endpoints/{endpoint_id}/test` | `webhooks:manage` |
|
|
174
|
+
| `hr.webhookEndpoints.deliveries(endpointId, query)` | `GET /api/v2/webhook-endpoints/{endpoint_id}/deliveries` | `webhooks:manage` |
|
|
175
|
+
| `hr.webhookEndpoints.redeliver(endpointId, deliveryId)` | `POST /api/v2/webhook-endpoints/{endpoint_id}/deliveries/{delivery_id}/redeliver` | `webhooks:manage` |
|
|
176
|
+
<!-- END GENERATED ENDPOINTS -->
|
|
177
|
+
|
|
178
|
+
## Data rooms
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
// NDAs need the Max plan (or the Deal Room Pass / trial)
|
|
182
|
+
const { nda_available, ndas } = await hr.ndas.list();
|
|
183
|
+
|
|
184
|
+
const room = await hr.dataRooms.create({
|
|
185
|
+
name: 'Project Falcon',
|
|
186
|
+
folders: ['1. Corporate', '2. Financials', '3. Legal'],
|
|
187
|
+
settings: { download: false, watermark: true, nda: nda_available, nda_template: 'investor', nda_mode: 'click' },
|
|
188
|
+
});
|
|
189
|
+
console.log(room.links.guest_link); // the room's shareable link
|
|
190
|
+
|
|
191
|
+
await hr.dataRooms.documents.add(room.id, { document_ids: [250, 251], folder_id: room.folders[1]!.id });
|
|
192
|
+
|
|
193
|
+
const guest = await hr.dataRooms.guests.invite(room.id, { email: 'jane@example.com' });
|
|
194
|
+
console.log(guest.link); // Jane's own link – also e-mailed to her unless send_invite: false
|
|
195
|
+
|
|
196
|
+
await hr.dataRooms.guests.list(room.id);
|
|
197
|
+
await hr.dataRooms.guests.remove(room.id, { email: 'jane@example.com' });
|
|
198
|
+
await hr.dataRooms.update(room.id, { settings: { expires_on: '2026-12-31' } });
|
|
199
|
+
|
|
200
|
+
// Deleting a room (and its guest access) needs an explicit confirmation
|
|
201
|
+
await hr.dataRooms.delete(room.id, { confirm: true });
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Links and views
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
// Every share link of the team – filter by document_id, folder_id, room_id, label, status, created_after/_before
|
|
208
|
+
for (let page: number | null = 1; page !== null; ) {
|
|
209
|
+
const res = await hr.links.list({ document_id: 250, status: 'active', page, limit: 200 });
|
|
210
|
+
for (const link of res.links) console.log(link.id, link.label, link.views, link.url);
|
|
211
|
+
page = res.next_page;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Who opened one link, when, for how long
|
|
215
|
+
const { views } = await hr.links.views(264, { limit: 100 });
|
|
216
|
+
for (const v of views) console.log(v.viewer, v.opened_at, v.seconds_read, v.pages_read);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Errors
|
|
220
|
+
|
|
221
|
+
Every error is a `HelpRangeError` with `status`, `code` (the API's machine-readable reason, when it sends one),
|
|
222
|
+
`message` and the parsed `body`:
|
|
223
|
+
|
|
224
|
+
| Class | When |
|
|
225
|
+
| --- | --- |
|
|
226
|
+
| `BadRequestError` | 400 – e.g. a duplicate link label, `sandbox_email_domain` |
|
|
227
|
+
| `AuthenticationError` | 401 – `invalid_key`, `key_revoked`, `key_expired`, `key_owner_removed` |
|
|
228
|
+
| `PaymentRequiredError` | 402 – the plan does not include the API / feature |
|
|
229
|
+
| `PermissionDeniedError` | 403 – `insufficient_scope` (see `err.requiredScope`) |
|
|
230
|
+
| `NotFoundError` | 404 |
|
|
231
|
+
| `ConflictError` | 409 – e.g. a frozen room, the team already has a webhook endpoint |
|
|
232
|
+
| `IdempotencyKeyInProgressError` | 409 `idempotency_key_in_progress` (a `ConflictError`) – still running after the in-progress retries |
|
|
233
|
+
| `UnprocessableEntityError` | 422 |
|
|
234
|
+
| `IdempotencyKeyReusedError` | 422 `idempotency_key_reused` (an `UnprocessableEntityError`) – the key was used for a different request |
|
|
235
|
+
| `PayloadTooLargeError` | 413 – `FILE_TOO_LARGE`, `REQUEST_TOO_LARGE` |
|
|
236
|
+
| `LockedError` | 423 – e.g. `ROOM_FROZEN` |
|
|
237
|
+
| `RateLimitError` | 429 – `RATE_LIMITED`, `DAILY_LIMIT`, `PAUSED`, `UPLOAD_LIMIT` (see `err.retryAfter`) |
|
|
238
|
+
| `InternalServerError` | 5xx |
|
|
239
|
+
| `APIConnectionError` / `APITimeoutError` | no HTTP answer / timed out |
|
|
240
|
+
| `WebhookVerificationError` | `verifyWebhook()` rejected a delivery |
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
import { PermissionDeniedError, RateLimitError } from 'helprange';
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
await hr.dataRooms.guests.invite(10, { email: 'jane@example.com' });
|
|
247
|
+
} catch (err) {
|
|
248
|
+
if (err instanceof PermissionDeniedError && err.code === 'insufficient_scope') {
|
|
249
|
+
console.error(`The key needs the ${err.requiredScope} scope`);
|
|
250
|
+
} else if (err instanceof RateLimitError) {
|
|
251
|
+
console.error(`Over a limit (${err.code}); retry in ${err.retryAfter}s`);
|
|
252
|
+
} else throw err;
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Retries and limits
|
|
257
|
+
|
|
258
|
+
Every `POST` and `PATCH` carries an `Idempotency-Key` header – a new UUID v4 per call, **the same one for every retry
|
|
259
|
+
of that call**. The API runs a keyed request once and answers a repeat with the stored response (header
|
|
260
|
+
`Idempotent-Replayed: true`), so a retried write never creates a second data room, share link or invitation.
|
|
261
|
+
|
|
262
|
+
- **429** (any method) – the API did not carry out the request. The client waits `Retry-After` and retries, unless the
|
|
263
|
+
wait is longer than `maxRetryAfter` (default 60 s – e.g. a daily cap or a paused API), in which case it throws
|
|
264
|
+
`RateLimitError` at once.
|
|
265
|
+
- **408 / 5xx, connection errors and timeouts** – retried for `GET` and for keyed `POST` / `PATCH`, with exponential
|
|
266
|
+
backoff (0.5 s, 1 s, 2 s … up to 8 s, with jitter; a short `Retry-After` is respected).
|
|
267
|
+
- **409 `idempotency_key_in_progress`** – the first attempt is still running on the server (typically after a timeout).
|
|
268
|
+
The client waits with backoff (~1, 2, 4, 8, 8 s) and asks again, up to 5 times, without using up `maxRetries`; then
|
|
269
|
+
it throws `IdempotencyKeyInProgressError`.
|
|
270
|
+
- **422 `idempotency_key_reused`** – never retried; throws `IdempotencyKeyReusedError`. It means the key was used in the
|
|
271
|
+
last 24 hours for a different method, path or body.
|
|
272
|
+
|
|
273
|
+
Pass your own key to make a write safe to repeat across processes or restarts (e.g. a job id):
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
await hr.dataRooms.create({ name: 'Project Falcon' }, { idempotencyKey: `deal-${deal.id}-room` });
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Keys are kept for 24 hours, per API key. `hr.share()` with an `idempotencyKey` uses it for the upload and
|
|
280
|
+
`<key>:link` for the share link.
|
|
281
|
+
|
|
282
|
+
`maxRetries` (default 2) and `timeout` (default 60 000 ms per attempt) can be set on the client or per request.
|
|
283
|
+
After each response `hr.rateLimit` holds `{ limit, remaining, reset, livemode }` from the `X-RateLimit-*` headers.
|
|
284
|
+
Team limits: 3,600 requests an hour, 120 a minute, 300 writes an hour, 2,000 share links and 1,000 guests a day.
|
|
285
|
+
|
|
286
|
+
## Webhooks
|
|
287
|
+
|
|
288
|
+
HelpRange POSTs signed JSON events (`document.opened`, `file_request.uploaded`, `room.access_requested`,
|
|
289
|
+
`nda.accepted`, `document.acknowledged`, `offer.accepted`, `offer.declined`, `offer.contact_requested`) to the endpoint
|
|
290
|
+
you set in Settings → Integrations or through `hr.webhookEndpoints`. Verify each delivery with the **raw** body:
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
import express from 'express';
|
|
294
|
+
import { verifyWebhook, WebhookVerificationError } from 'helprange';
|
|
295
|
+
|
|
296
|
+
const app = express();
|
|
297
|
+
app.post('/webhooks/helprange', express.raw({ type: 'application/json' }), (req, res) => {
|
|
298
|
+
try {
|
|
299
|
+
const event = verifyWebhook(req.body, req.headers, process.env.HELPRANGE_WEBHOOK_SECRET!);
|
|
300
|
+
// deduplicate on event.id – retries carry the same id
|
|
301
|
+
if (event.event === 'document.opened') console.log(event.data);
|
|
302
|
+
res.sendStatus(200); // answer 2xx within 10 seconds
|
|
303
|
+
} catch (err) {
|
|
304
|
+
res.sendStatus(err instanceof WebhookVerificationError ? 400 : 500);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
The scheme: `HelpRange-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>`. `verifyWebhook` accepts a
|
|
310
|
+
timestamp within 5 minutes (`{ tolerance }` to change it) and any `v1` that matches – during the 24 hours after a
|
|
311
|
+
secret rotation HelpRange sends two. Pass an array of secrets if you rotate on your side. `signWebhookPayload()` builds
|
|
312
|
+
a valid header for your tests. Failed deliveries are retried for about 3 days; see the
|
|
313
|
+
[webhooks guide](https://www.helprange.com/developers/webhooks).
|
|
314
|
+
|
|
315
|
+
Manage the endpoint from code:
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
const ep = await hr.webhookEndpoints.create({ url: 'https://example.com/webhooks/helprange' });
|
|
319
|
+
console.log(ep.secret); // shown once – store it
|
|
320
|
+
await hr.webhookEndpoints.sendTest(ep.id); // a test event, delivered right away
|
|
321
|
+
const { deliveries } = await hr.webhookEndpoints.deliveries(ep.id, { status: 'failed' });
|
|
322
|
+
if (deliveries[0]) await hr.webhookEndpoints.redeliver(ep.id, deliveries[0].id);
|
|
323
|
+
const { secret } = await hr.webhookEndpoints.rotateSecret(ep.id); // the old secret keeps signing for 24 hours
|
|
324
|
+
await hr.webhookEndpoints.update(ep.id, { enabled: true }); // turn it back on after 410 Gone / repeated failures
|
|
325
|
+
await hr.webhookEndpoints.delete(ep.id);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Examples
|
|
329
|
+
|
|
330
|
+
Runnable scripts in [`examples/`](examples):
|
|
331
|
+
|
|
332
|
+
- [`share-pdf.ts`](examples/share-pdf.ts) – share a PDF and get a link
|
|
333
|
+
- [`who-viewed.ts`](examples/who-viewed.ts) – every link of a document and who viewed each one, plus time per page
|
|
334
|
+
- [`data-room.ts`](examples/data-room.ts) – create a data room with folders and an NDA, add documents, invite guests
|
|
335
|
+
and print their links, read the index and Q&A
|
|
336
|
+
- [`webhook-endpoints.ts`](examples/webhook-endpoints.ts) – create / update the webhook endpoint, send a test event,
|
|
337
|
+
redeliver a failure, rotate the secret
|
|
338
|
+
- [`webhook-server.ts`](examples/webhook-server.ts) – verify webhooks on plain `node:http`
|
|
339
|
+
|
|
340
|
+
## Development
|
|
341
|
+
|
|
342
|
+
```sh
|
|
343
|
+
# Node 22 (e.g. export PATH="$HOME/.nvm/versions/node/v22.23.4/bin:$PATH"); the package itself supports Node 18+
|
|
344
|
+
npm install
|
|
345
|
+
npm run typecheck # src + examples
|
|
346
|
+
npm test # builds ESM + CJS, runs node:test with a mocked fetch (no network)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
`src/generated/` is generated from the OpenAPI spec – don't edit it by hand; see the generator in the
|
|
350
|
+
`helprange-sdks` workspace.
|
|
351
|
+
|
|
352
|
+
## License
|
|
353
|
+
|
|
354
|
+
MIT © HelpRange
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { FinalRequest, RequestOptions, Requester } from './core.js';
|
|
2
|
+
import { type Resources } from './generated/resources.js';
|
|
3
|
+
import type { CreateLinkBody, CreateLinkResponse, UploadFileBody, UploadFileResponse } from './generated/types.js';
|
|
4
|
+
export declare const DEFAULT_BASE_URL = "https://www.helprange.com";
|
|
5
|
+
export declare const DEFAULT_TIMEOUT_MS = 60000;
|
|
6
|
+
export declare const DEFAULT_MAX_RETRIES = 2;
|
|
7
|
+
/** Longer Retry-After waits (a daily cap, a paused API) are not slept through; the RateLimitError is thrown. */
|
|
8
|
+
export declare const DEFAULT_MAX_RETRY_AFTER_S = 60;
|
|
9
|
+
type FetchLike = (input: string, init: RequestInit) => Promise<Response>;
|
|
10
|
+
export interface HelpRangeOptions {
|
|
11
|
+
/** Team API key (`hr_live_…` or `hr_test_…`). Default: the HELPRANGE_API_KEY environment variable. */
|
|
12
|
+
apiKey?: string;
|
|
13
|
+
/** API origin. Default `https://www.helprange.com` (or HELPRANGE_BASE_URL). */
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
/** Timeout of each attempt in milliseconds. Default 60000. */
|
|
16
|
+
timeout?: number;
|
|
17
|
+
/** How many times to retry a failed request (see README → Retries). Default 2. */
|
|
18
|
+
maxRetries?: number;
|
|
19
|
+
/** Longest Retry-After (seconds) the client waits for on a 429 before giving up. Default 60. */
|
|
20
|
+
maxRetryAfter?: number;
|
|
21
|
+
/** A fetch implementation (default: the global fetch of Node 18+). Handy for tests and proxies. */
|
|
22
|
+
fetch?: FetchLike;
|
|
23
|
+
/** Headers sent with every request. */
|
|
24
|
+
defaultHeaders?: Record<string, string>;
|
|
25
|
+
}
|
|
26
|
+
/** Usage-limit headers of the latest response (`X-RateLimit-*`, `X-HelpRange-Livemode`). */
|
|
27
|
+
export interface RateLimitInfo {
|
|
28
|
+
limit?: number;
|
|
29
|
+
remaining?: number;
|
|
30
|
+
/** Unix time when the current hour ends. */
|
|
31
|
+
reset?: number;
|
|
32
|
+
livemode?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** Arguments of {@link HelpRange.share}: an upload plus the settings of the new share link. */
|
|
35
|
+
export interface ShareParams extends UploadFileBody {
|
|
36
|
+
/** Settings of the share link (label, passcode, expires_on, watermark, …). */
|
|
37
|
+
link?: CreateLinkBody;
|
|
38
|
+
}
|
|
39
|
+
export interface ShareResult {
|
|
40
|
+
document: UploadFileResponse;
|
|
41
|
+
link: CreateLinkResponse;
|
|
42
|
+
}
|
|
43
|
+
/** How often a 409 idempotency_key_in_progress is retried (on top of maxRetries, with backoff of ~1-8 s). */
|
|
44
|
+
export declare const IN_PROGRESS_MAX_RETRIES = 5;
|
|
45
|
+
/** Seconds from a Retry-After header (delta-seconds or an HTTP date). */
|
|
46
|
+
export declare function parseRetryAfter(value: string | null | undefined, nowMs?: number): number | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* The HelpRange API client.
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* import { HelpRange } from 'helprange';
|
|
52
|
+
* const hr = new HelpRange({ apiKey: process.env.HELPRANGE_API_KEY });
|
|
53
|
+
* const { documents } = await hr.documents.list();
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* Resources mirror the tags of the API reference: `account`, `folders`, `documents`, `links`, `triggers`, `dataRooms`
|
|
57
|
+
* (with `guests`, `documents`, `folders`, … sub-resources), `ndas`, `signatureRequests`, `offers`, `store`,
|
|
58
|
+
* `leadForms`, `activity`, `webhookEndpoints`.
|
|
59
|
+
*/
|
|
60
|
+
export declare class HelpRange implements Requester {
|
|
61
|
+
#private;
|
|
62
|
+
readonly baseUrl: string;
|
|
63
|
+
readonly timeout: number;
|
|
64
|
+
readonly maxRetries: number;
|
|
65
|
+
readonly maxRetryAfter: number;
|
|
66
|
+
/** Usage-limit headers of the latest response. */
|
|
67
|
+
rateLimit: RateLimitInfo;
|
|
68
|
+
/** Spec version this SDK was generated from. */
|
|
69
|
+
static readonly API_VERSION = "2026-11-15";
|
|
70
|
+
static readonly VERSION = "0.1.0";
|
|
71
|
+
constructor(options?: HelpRangeOptions);
|
|
72
|
+
/** True for a test-mode key (`hr_test_…`): every call works on your team's sandbox, never on live data. */
|
|
73
|
+
get testMode(): boolean;
|
|
74
|
+
/** The visible prefix of the key, e.g. `hr_live_Ab3x…` (never the whole key). */
|
|
75
|
+
get keyHint(): string;
|
|
76
|
+
/**
|
|
77
|
+
* Uploads a file and creates a share link for it in one call.
|
|
78
|
+
*
|
|
79
|
+
* ```ts
|
|
80
|
+
* const { link } = await hr.share({ file: await readFile('deck.pdf'), filename: 'deck.pdf',
|
|
81
|
+
* link: { label: 'ACME - Anna', expires_on: '2026-12-31', allow_download: false } });
|
|
82
|
+
* console.log(link.url);
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
share(params: ShareParams, options?: RequestOptions): Promise<ShareResult>;
|
|
86
|
+
/**
|
|
87
|
+
* Sends a request and returns the parsed JSON. The resource methods call this; use it directly for endpoints
|
|
88
|
+
* this SDK version does not wrap yet.
|
|
89
|
+
*/
|
|
90
|
+
request<T>(req: FinalRequest): Promise<T>;
|
|
91
|
+
}
|
|
92
|
+
export interface HelpRange extends Resources {
|
|
93
|
+
}
|
|
94
|
+
export {};
|