# HelpRange API – full reference > HelpRange is secure document sharing and virtual data rooms: upload documents, create tracked and protected share links, run data rooms (guests, NDA, Q&A, request lists), send documents for e-signature and read page-by-page analytics. Developers use the REST API (team API keys with scopes and a test-mode sandbox), signed webhooks, and a remote MCP server with OAuth for AI assistants. Generated from the OpenAPI document (https://www.helprange.com/developers/openapi.json, version 2026-11-15), the MCP server's tool list and the webhook code. Index: https://www.helprange.com/developers/llms.txt ## Overview Base URL: `https://www.helprange.com/api/v2` (v1: `https://www.helprange.com/public-api`). REST API of HelpRange: upload documents, create protected share links, run data rooms (guests, folders, Q&A, request lists), send documents for e-signature, create offers and read page-by-page analytics. **Authentication.** Create a team API key in Settings → API keys and send it as `Authorization: Bearer hr_live_…` (also accepted: `X-API-Key: `, `Authorization: Api-Key `; the older `/public-api` endpoints take the key in `Authorization`, bare or as `Bearer`). A key acts as the administrator who created it, with their rights in the panel, and only within its **scopes** (`documents:read`, `documents:write`, `links:read`, `links:write`, `rooms:read`, `rooms:write`, `analytics:read`, `team:read`, `webhooks:manage`); each operation names the scope it needs in `x-required-scope`. A key without it: 403 `{"error", "code": "insufficient_scope", "required_scope"}`. The legacy personal key (profile page) and OAuth access tokens of the HelpRange MCP connector (`Authorization: Bearer `) keep full access. Keep keys on your server. **Test mode.** Keys starting with `hr_test_` work on a separate sandbox of your team with sample data – never on live data. No e-mail is sent (test mode accepts only addresses at reserved test domains such as example.com; others: 400 `sandbox_email_domain`), nothing reaches Stripe, and sandbox activity sends no webhooks. Every response of a team key carries `"livemode": true|false` (JSON objects) and the `X-HelpRange-Livemode` header. Sandbox data is deleted 30 days after the sandbox was created or reset. **Retries.** There is no Idempotency-Key header. GET requests are safe to retry. A share link's label is unique per document, so retrying "Create a share link" with the same label answers 400 instead of making a second link; inviting a guest who is already in the room answers 200 with `already_a_guest: true`. **Webhooks.** Signed JSON events (`HelpRange-Signature: t=,v1=.">`), retried for about 3 days, with a 30-day delivery log – see `webhooks` in this document and https://www.helprange.com/developers/webhooks. Endpoints are set up in Settings → Integrations. **Plans.** REST API v2 and /public-api are part of the Max and Unlimited plans, the Deal Room Pass and the trial (`/me`, `/folders` and buyer access also work on Pro). Other plans: 402 with `{"error": "..."}`. **Errors.** JSON `{"error": "..."}` (some with a `code`); v1 uses `{"status", "message"}` or `{"result": "failure", "reason"}`. **Usage limits** (per team, all keys and connected apps together; clock hour / minute, UTC): 3,600 requests an hour and 120 a minute; 300 write requests an hour; 2,000 share links and 1,000 guests created through the API a day; 100 e-mails to other people an hour and 500 a day (at most 50 recipients per request); per IP address 4,000 requests an hour and 60 with a missing or invalid key; more than 300 failed requests in an hour pause the team's API for 15 minutes. Over a limit: 429 with `{"error", "code", "retry_after"}` and `Retry-After`. Every authenticated response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`. ## Authentication, API keys and scopes Create keys in Settings → API keys (administrators). Format `hr_live_` or `hr_test_` + 40 letters and digits, shown once (only a SHA-256 hash is stored). Send `Authorization: Bearer ` (also `X-API-Key: ` or `Authorization: Api-Key `; v1 /public-api: the key in `Authorization`, bare or Bearer). A key acts as the administrator who created or last rolled it. Roll a key with an overlap of none, 24 hours or 7 days; revoke at once; optional expiry date. The legacy personal key (profile page) and MCP OAuth tokens keep full access. | Scope | Allows | |---|---| | `documents:read` | List folders, documents, subfolders, e-signature requests and files uploaded by clients. | | `documents:write` | Upload documents, create and change folders and subfolders, send and manage e-signature requests. | | `links:read` | Read share links of documents, offers and lead-form templates. | | `links:write` | Create share links, give or remove buyer access, create and change offers. | | `rooms:read` | Read data rooms: details, index, guests' access requests, Q&A, request lists, folders, documents. | | `rooms:write` | Change data rooms: invite guests, answer Q&A, request lists, room folders and documents. | | `analytics:read` | Read views, statistics, activity, offer responses and store sales. | | `webhooks:manage` | Reserved for managing webhook endpoints through the API (no endpoint uses it yet – webhooks are set up in Settings → Integrations). | | `team:read` | Read the team: who the key belongs to, the plan and its limits. | Key errors: 401 `invalid_key`, `key_revoked`, `key_expired`, `key_owner_removed`; 403 `insufficient_scope` with `required_scope` and `key_scopes`. ### Test mode (sandbox) Keys starting with `hr_test_` act on a separate sandbox of the team, created on the first test request with sample data (two documents with links and sample views, a data room with two guests). No e-mail is sent (only addresses at reserved test domains such as example.com, example.org or *.test are accepted – others: 400 `sandbox_email_domain`), nothing reaches Stripe, sandbox activity sends no webhooks, and responses carry `"livemode": false` and `X-HelpRange-Livemode: false`. "Reset sandbox" in Settings → API keys starts over; sandbox data is deleted 30 days after the sandbox was created or reset. API access follows the live team's plan. ## Errors - 400: Invalid input – the message says which parameter. - 401: Missing or invalid API key / OAuth token. Team keys: code invalid_key, key_revoked, key_expired or key_owner_removed. - 402: Your plan does not include the REST API (or this feature). - 403: v1: the key is invalid, or the folder / file / guest / data room is not yours (HTML page unless you send Accept: application/json). Team keys (v1 and v2): code insufficient_scope with required_scope – the key lacks the scope this endpoint needs. - 404: Not found – or not yours (the API never says which). - 405: Wrong HTTP method for this endpoint. - 409: The data room is frozen, or the signed PDF is not ready yet. - 413: The file or the request body is over the upload limit (code FILE_TOO_LARGE / REQUEST_TOO_LARGE). - 423: The target data room is frozen (code ROOM_FROZEN). - 429: A usage limit was reached – wait Retry-After seconds. ## REST API operations ## Account Who is connected, the plan and its limits. ### Who is connected `GET /api/v2/me` The user the API key (or OAuth token) belongs to – the "test" call of Zapier, Make and n8n. Also works on Pro. Plans: Pro, Max, Unlimited, Deal Room Pass, trial; scope: `team:read`; reference: https://www.helprange.com/developers/api/get-me. Responses: 200, 401, 402, 403, 405, 429. Example (200): ```json { "email": "ann@acme.com", "name": "Ann Admin", "team": "Acme", "plan": "max" } ``` ```bash curl -X GET https://www.helprange.com/api/v2/me \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Plan, features and limits `GET /api/v2/plan` The team's plan, which features it includes (and the plan each other feature needs), AI requests left, reader views left (Free and the trial), the first-week view cap and data rooms left. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `team:read`; reference: https://www.helprange.com/developers/api/get-plan. Responses: 200, 401, 402, 403, 405, 429. Example (200): ```json { "plan": "Max", "plan_key": "max", "you_are_admin": true, "features": [ { "feature": "E-signature", "available": true, "plan_needed": "Pro" } ], "ai_requests": { "used": 12, "limit": 500, "left": 488, "period": "calendar month" }, "reader_views": null, "first_week_cap": null, "data_rooms_left": null, "mcp_server": true, "rest_api": true, "pricing_url": "https://www.helprange.com/pricing" } ``` ```bash curl -X GET https://www.helprange.com/api/v2/plan \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ## Folders Folders and their subfolders. ### List folders `GET /api/v2/folders` Folders you can open, by name (administrators: every folder of the team; members: the folders they are a member or the owner of). Also works on Pro. Plans: Pro, Max, Unlimited, Deal Room Pass, trial; scope: `documents:read`; reference: https://www.helprange.com/developers/api/list-folders. Responses: 200, 401, 402, 403, 405, 429. Example (200): ```json { "folders": [ { "id": 31, "name": "Legal" }, { "id": 44, "name": "Sales" } ] } ``` ```bash curl -X GET https://www.helprange.com/api/v2/folders \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Get a folder `GET /api/v2/folders/{folder_id}` A folder's name, document count and whether subfolders are on. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:read`; reference: https://www.helprange.com/developers/api/get-folder. Parameters: - `folder_id` (path, integer, required): Folder id (from List folders). Responses: 200, 401, 402, 403, 404, 405, 429. Example (200): ```json { "id": 31, "name": "Legal", "documents": 16, "subfolders_available": true, "subfolders_enabled": true } ``` ```bash curl -X GET https://www.helprange.com/api/v2/folders/31 \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Turn subfolders on or off `POST /api/v2/folders/{folder_id}` Subfolders are off by default. Turning them off moves every document back to the top of the folder (nothing is deleted) and deletes the subfolders. Answers with the folder. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:write`; reference: https://www.helprange.com/developers/api/set-folder-subfolders. Parameters: - `folder_id` (path, integer, required): Folder id (from List folders). Body: - `subfolders_enabled` (boolean, required): true to turn subfolders on, false to turn them off. Responses: 200, 400, 401, 402, 403, 404, 405, 429. Example (200): ```json { "id": 31, "name": "Legal", "documents": 16, "subfolders_available": true, "subfolders_enabled": true } ``` ```bash curl -X POST https://www.helprange.com/api/v2/folders/31 \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"subfolders_enabled": true}' ``` ### List subfolders `GET /api/v2/folders/{folder_id}/subfolders` Subfolders of a folder with their document counts. Subfolders only group documents – the folder's protection, links and members apply to them unchanged. One level: a subfolder never holds other subfolders. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:read`; reference: https://www.helprange.com/developers/api/list-subfolders. Parameters: - `folder_id` (path, integer, required): Folder id (from List folders). Responses: 200, 400, 401, 402, 403, 404, 405, 429. Example (200): ```json { "folder_id": 31, "folder": "Legal", "subfolders_available": true, "subfolders_enabled": true, "subfolders": [ { "id": 7, "name": "Contracts", "position": 0, "folder_id": 31, "documents_count": 12 } ], "documents_at_top": 4 } ``` ```bash curl -X GET https://www.helprange.com/api/v2/folders/31/subfolders \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Create a subfolder `POST /api/v2/folders/{folder_id}/subfolders` Creates a subfolder in a folder. Refused with 400 while subfolders are off for the folder (turn them on with Turn subfolders on or off). Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:write`; reference: https://www.helprange.com/developers/api/create-subfolder. Parameters: - `folder_id` (path, integer, required): Folder id (from List folders). Body: - `name` (string, required): Subfolder name. (max length 100) Responses: 201, 400, 401, 402, 403, 404, 405, 429. Example (201): ```json { "id": 7, "name": "Contracts", "position": 0, "folder_id": 31, "documents_count": 0, "folder": "Legal" } ``` ```bash curl -X POST https://www.helprange.com/api/v2/folders/31/subfolders \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"name": "Contracts"}' ``` ### Move documents to a subfolder `POST /api/v2/folders/{folder_id}/subfolders/move` Moves documents of the folder into one of its subfolders, or back to the top of the folder (subfolder_id null or missing). Ids that are not in this folder are listed in not_in_folder. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:write`; reference: https://www.helprange.com/developers/api/move-documents-to-subfolder. Parameters: - `folder_id` (path, integer, required): Folder id (from List folders). Body: - `document_ids` (array of integer, required): Documents of this folder (up to 100). Form posts: repeated fields or "1,2". - `subfolder_id` (integer or null): A subfolder of this folder; null = the top of the folder. Responses: 200, 400, 401, 402, 403, 404, 405, 429. Example (200): ```json { "moved": 2, "folder_id": 31, "subfolder_id": 7, "subfolder": "Contracts", "not_in_folder": [] } ``` ```bash curl -X POST https://www.helprange.com/api/v2/folders/31/subfolders/move \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"document_ids": [252, 253], "subfolder_id": 7}' ``` ### Rename a subfolder `POST /api/v2/subfolders/{subfolder_id}` Renames a subfolder. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:write`; reference: https://www.helprange.com/developers/api/rename-subfolder. Parameters: - `subfolder_id` (path, integer, required): Subfolder id (from List subfolders). Body: - `name` (string, required): New name. (max length 100) Responses: 200, 400, 401, 402, 403, 404, 405, 429. Example (200): ```json { "id": 7, "name": "Signed contracts", "position": 0, "folder_id": 31, "folder": "Legal" } ``` ```bash curl -X POST https://www.helprange.com/api/v2/subfolders/7 \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"name": "Signed contracts"}' ``` ### Delete a subfolder `POST /api/v2/subfolders/{subfolder_id}/delete` Deletes the subfolder only – its documents go back to the top of the folder; no document is deleted. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:write`; reference: https://www.helprange.com/developers/api/delete-subfolder. Parameters: - `subfolder_id` (path, integer, required): Subfolder id (from List subfolders). Responses: 200, 400, 401, 402, 403, 404, 405, 429. Example (200): ```json { "deleted": true, "id": 7, "folder_id": 31, "folder": "Legal", "moved_to_folder": 12 } ``` ```bash curl -X POST https://www.helprange.com/api/v2/subfolders/7/delete \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ## Documents Uploads, documents, statistics and share links. ### List documents `GET /api/v2/documents` Your latest 100 documents, newest first, in folders you can open. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:read`; reference: https://www.helprange.com/developers/api/list-documents. Parameters: - `folder_id` (query, integer): Only documents of this folder. - `subfolder` (query, string): A subfolder id, or "none" for documents in no subfolder. subfolder_id is accepted too. Responses: 200, 400, 401, 402, 403, 405, 429. Example (200): ```json { "documents": [ { "id": 250, "name": "proposal.pdf", "folder_id": 31, "type": "document", "pages": 12, "default_link": "https://www.helprange.com/view/471dcc98874d4d0cb7c3eb1874c4e3db/proposal.pdf", "subfolder_id": 7, "subfolder": "Contracts", "path": "Legal › Contracts" } ] } ``` ```bash curl -X GET 'https://www.helprange.com/api/v2/documents?folder_id=31' \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Upload a file `POST /api/v2/files` Uploads a file into a folder (folder_id, or your default folder). Send the file as multipart "file" (recommended), as a public "file_url" HelpRange downloads (within 30 seconds), or as "content_base64" with "filename" in a JSON body. Limit: the team's upload limit, 100 MB per file (larger files only in the panel). Over it: 413 with code FILE_TOO_LARGE or REQUEST_TOO_LARGE; the team's daily upload limit: 429 with code UPLOAD_LIMIT. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:write`; reference: https://www.helprange.com/developers/api/upload-file. Body (One of: multipart "file", "file_url" or "content_base64".): - `file` (multipart file): the file (recommended). - `folder_id` (integer): Target folder (default: your default folder). - `file_url` (string (uri)): A public URL of the file. - `content_base64` (string): The file, base64-encoded. - `filename` (string): File name (default: the uploaded / downloaded name, or document.pdf). Responses: 201, 400, 401, 402, 403, 404, 405, 413, 423, 429. Example (201): ```json { "id": 250, "name": "proposal.pdf", "folder_id": 31, "type": "document", "pages": 12, "default_link": "https://www.helprange.com/view/471dcc98874d4d0cb7c3eb1874c4e3db/proposal.pdf" } ``` ```bash curl -X POST https://www.helprange.com/api/v2/files \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -F 'file=@proposal.pdf' \ -F folder_id=31 ``` ### Create a share link `POST /api/v2/files/{file_id}/links` A new tracked share link for a document with its own settings. A setting your plan does not include is refused with the plan that has it. Counts towards the daily cap of 2,000 links created through the API. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `links:write`; reference: https://www.helprange.com/developers/api/create-link. Parameters: - `file_id` (path, integer, required): Document id (from List documents). Body: - `label` (string): Who the link is for, e.g. "ACME - Anna" (shown in statistics). Unique per document: a label the document already has is refused with 400. Default: a label with the date and time. - `allow_download` (boolean or integer or string): Allow download & print. Default: blocked on Pro and up, allowed on Free. true / false (also 1 / 0 or "true" / "false"). - `passcode` (string): Password the viewer must enter (Pro and higher). Surrounding spaces are dropped; only spaces = no passcode. At most 30 characters. - `expires_on` (string (date)): Last day the link works, YYYY-MM-DD (Pro and higher). Today or later – a day in the past or an unreadable date is refused with 400. - `views_limit` (integer or null): Maximum number of views. 0, null or empty = no limit; a negative or non-whole number is refused with 400. (min 0) - `watermark` (boolean or integer or string): Watermark pages with the viewer's e-mail and date (Pro and higher). true / false (also 1 / 0 or "true" / "false"). - `require_email` (boolean or integer or string): Ask for the viewer's e-mail before opening (Pro and higher). true / false (also 1 / 0 or "true" / "false"). - `disallow_forwarding` (boolean or integer or string): Only the first person who opens it can use it. true / false (also 1 / 0 or "true" / "false"). - `one_viewer_at_a_time` (boolean or integer or string): Only one person can view at the same time. true / false (also 1 / 0 or "true" / "false"). Responses: 201, 400, 401, 402, 403, 404, 405, 429. Example (201): ```json { "id": 264, "url": "https://www.helprange.com/view/8956d1e7b91b47e196a8040b1d4894f8/proposal.pdf", "label": "ACME - Anna", "allow_download": false, "passcode": true, "expires_on": "2026-12-31", "views_limit": null, "watermark": true, "require_email": true, "disallow_forwarding": false, "one_viewer_at_a_time": false, "document_id": 250, "document": "proposal.pdf" } ``` ```bash curl -X POST https://www.helprange.com/api/v2/files/250/links \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"label": "ACME - Anna", "passcode": "oak-41", "expires_on": "2026-12-31", "watermark": true, "require_email": true}' ``` ### Document statistics `GET /api/v2/documents/{file_id}` Views, downloads, total reading time, average seconds per page and the latest views (who, when, how long) of one document. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:read`; reference: https://www.helprange.com/developers/api/get-document. Parameters: - `file_id` (path, integer, required): Document id (from List documents). - `recent_views` (query, integer): How many latest views to return, 1–100 (default 20). Responses: 200, 401, 402, 403, 404, 405, 429. Example (200): ```json { "id": 250, "name": "proposal.pdf", "type": "document", "pages": 12, "views": 39, "downloads": 2, "total_seconds_read": 1830, "last_viewed_at": "2026-11-09T14:02:11+00:00", "share_link": "https://www.helprange.com/view/471dcc98874d4d0cb7c3eb1874c4e3db/proposal.pdf", "avg_seconds_per_page": [ { "page": 1, "avg_seconds": 21 }, { "page": 2, "avg_seconds": 64 } ], "recent_views": [ { "viewer": "anna@client.com", "opened_at": "2026-11-09T14:02:11+00:00", "seconds_read": 312, "country": "DE", "downloaded": false, "document": "proposal.pdf", "document_id": 250 } ], "folder_id": 31, "folder": "Legal", "subfolder_id": null, "subfolder": null, "path": "Legal" } ``` ```bash curl -X GET 'https://www.helprange.com/api/v2/documents/250?recent_views=5' \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### List share links of a document `GET /api/v2/documents/{file_id}/links` Every share link of a document with its settings. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `links:read`; reference: https://www.helprange.com/developers/api/list-document-links. Parameters: - `file_id` (path, integer, required): Document id (from List documents). Responses: 200, 401, 402, 403, 404, 405, 429. Example (200): ```json { "document": "proposal.pdf", "links": [ { "id": 264, "url": "https://www.helprange.com/view/8956d1e7b91b47e196a8040b1d4894f8/proposal.pdf", "label": "ACME - Anna", "allow_download": false, "passcode": true, "expires_on": "2026-12-31", "views_limit": null, "watermark": true, "require_email": true, "disallow_forwarding": false, "one_viewer_at_a_time": false } ] } ``` ```bash curl -X GET https://www.helprange.com/api/v2/documents/250/links \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ## Triggers Polling triggers for Zapier, Make and n8n: newest first, each item with a stable id. ### Recent document views `GET /api/v2/views` Polling trigger "New document view": the latest 50 views of documents in your folders, newest first. Each item has a stable id (de-duplicate by it). The response is a JSON array. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `analytics:read`; reference: https://www.helprange.com/developers/api/list-recent-views. Responses: 200, 401, 402, 403, 405, 429. Example (200): ```json [ { "id": 98321, "opened_at": "2026-11-15T09:12:40+00:00", "viewer": "anna@client.com", "document_id": 250, "document": "proposal.pdf", "link_label": "ACME - Anna", "seconds_read": 312, "country": "DE", "downloaded": false } ] ``` ```bash curl -X GET https://www.helprange.com/api/v2/views \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Recent client uploads `GET /api/v2/uploads` Polling trigger "New file from a client": the latest 50 files uploaded through file requests and data room guest uploads, newest first. id is the upload's id; document_id the uploaded document. The response is a JSON array. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:read`; reference: https://www.helprange.com/developers/api/list-recent-uploads. Responses: 200, 401, 402, 403, 405, 429. Example (200): ```json [ { "id": 512, "name": "signed-loi.pdf", "folder_id": 31, "type": "document", "pages": 12, "default_link": "https://www.helprange.com/view/471dcc98874d4d0cb7c3eb1874c4e3db/proposal.pdf", "document_id": 270, "uploaded_by": "bob@fund.com", "uploaded_by_name": "Bob Buyer", "uploaded_at": "2026-11-15T08:01:00+00:00" } ] ``` ```bash curl -X GET https://www.helprange.com/api/v2/uploads \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ## Data rooms Data rooms, their index, guests, folders, documents, Q&A and request lists. ### List data rooms `GET /api/v2/data-rooms` Data rooms with guest and document counts (administrators: every room; members: the rooms they created). Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:read`; reference: https://www.helprange.com/developers/api/list-data-rooms. Responses: 200, 401, 402, 403, 405, 429. Example (200): ```json { "data_rooms": [ { "id": 10, "name": "Project Falcon", "guests": 4, "documents": 37, "pending_access_requests": 1, "expires_at": null, "frozen": false, "index_numbering": true, "folders_enabled": true } ] } ``` ```bash curl -X GET https://www.helprange.com/api/v2/data-rooms \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Data room details `GET /api/v2/data-rooms/{data_room_id}` Guests (views, last visit, NDA status), documents, freeze status with closing archives, and Q&A counts. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:read`; reference: https://www.helprange.com/developers/api/get-data-room. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). Responses: 200, 401, 402, 403, 404, 405, 429. Example (200): ```json { "id": 10, "name": "Project Falcon", "nda_required": "Falcon NDA", "folders_enabled": true, "approval_workflow": false, "guests": [ { "email": "buyer@bidco.com", "views": 23, "last_view_at": "2026-11-09T17:40:00+00:00", "nda": "signed", "nda_signed_at": "2026-11-02T10:00:00+00:00" } ], "pending_access_requests": 1, "documents": [ { "id": 250, "name": "teaser.pdf", "views": 11, "room_link_id": 901, "room_folder_id": null, "room_folder": null, "group": null, "approval": "published" } ], "expires_at": null, "index_numbering": true, "freeze": { "frozen": false, "guest_access": "full", "frozen_at": null, "archives": [] }, "qa": { "available": true, "open_questions": 3, "questions": 12, "request_items": 40, "guests_can_ask": true, "answers_need_approval": false, "guests_see_request_list": true } } ``` ```bash curl -X GET https://www.helprange.com/api/v2/data-rooms/10 \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Turn data room folders on or off `POST /api/v2/data-rooms/{data_room_id}` Folders inside a data room are off by default. Turning them off deletes the room's folders; their documents stay in the room at the top level. Answers with the room. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:write`; reference: https://www.helprange.com/developers/api/set-data-room-folders. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). Body: - `folders_enabled` (boolean, required): true to turn folders on, false to turn them off. Responses: 200, 400, 401, 402, 403, 404, 405, 409, 429. Example (200): ```json { "id": 10, "name": "Project Falcon", "nda_required": "Falcon NDA", "folders_enabled": true, "approval_workflow": false, "guests": [ { "email": "buyer@bidco.com", "views": 23, "last_view_at": "2026-11-09T17:40:00+00:00", "nda": "signed", "nda_signed_at": "2026-11-02T10:00:00+00:00" } ], "pending_access_requests": 1, "documents": [ { "id": 250, "name": "teaser.pdf", "views": 11, "room_link_id": 901, "room_folder_id": null, "room_folder": null, "group": null, "approval": "published" } ], "expires_at": null, "index_numbering": true, "freeze": { "frozen": false, "guest_access": "full", "frozen_at": null, "archives": [] }, "qa": { "available": true, "open_questions": 3, "questions": 12, "request_items": 40, "guests_can_ask": true, "answers_need_approval": false, "guests_see_request_list": true } } ``` ```bash curl -X POST https://www.helprange.com/api/v2/data-rooms/10 \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"folders_enabled": true}' ``` ### Data room index `GET /api/v2/data-rooms/{data_room_id}/index` The room's numbered index (1, 1.1 …) in the structure guests see – always one level of folders. With room folders on, folder nodes are the room's folders (room_folder_id); otherwise, with "Show Folders" on, the folder the documents come from (folder_id, plus subfolder_id for "Folder / Subfolder"). The numbers exist even while numbering is off for guests (index_numbering false). Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:read`; reference: https://www.helprange.com/developers/api/get-data-room-index. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). Responses: 200, 401, 402, 403, 404, 405, 429. Example (200): ```json { "data_room_id": 10, "index_numbering": true, "index": [ { "type": "folder", "number": "1", "name": "Financials", "folder_id": null, "room_folder_id": 5, "children": [ { "type": "document", "number": "1.1", "name": "pnl.xlsx", "document_id": 260, "room_link_id": 910, "room_folder_id": 5 } ] }, { "type": "document", "number": "2", "name": "teaser.pdf", "document_id": 250, "room_link_id": 901, "room_folder_id": null } ] } ``` ```bash curl -X GET https://www.helprange.com/api/v2/data-rooms/10/index \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Invite a guest `POST /api/v2/data-rooms/{data_room_id}/guests` Like "Add guest" in the panel: the invitation e-mail goes out when the room sends e-mail notifications and has a document. 201 for a new guest, 200 when they already are one, 409 when the room is frozen. Counts towards the daily cap of 1,000 guests and the API e-mail budget. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:write`; reference: https://www.helprange.com/developers/api/invite-data-room-guest. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). Body: - `email` (string, required): The guest's e-mail address. Responses: 201, 400, 401, 402, 403, 404, 405, 409, 429. Example (201): ```json { "id": 77, "email": "buyer@bidco.com", "data_room_id": 10, "invited": true, "already_a_guest": false } ``` ```bash curl -X POST https://www.helprange.com/api/v2/data-rooms/10/guests \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"email": "buyer@bidco.com"}' ``` ### Access requests `GET /api/v2/data-rooms/{data_room_id}/access-requests` People who asked to join the room. Approve or deny them on the data room page. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:read`; reference: https://www.helprange.com/developers/api/list-data-room-access-requests. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). - `status` (query, "pending" | "approved" | "denied" | "all"): Default pending. - `limit` (query, integer): 1–200 (default 50). Responses: 200, 401, 402, 403, 404, 405, 429. Example (200): ```json { "access_requests": [ { "id": 5, "data_room_id": 10, "data_room": "Project Falcon", "name": "Bob Buyer", "email": "bob@fund.com", "company": "Fund LP", "message": "We are bidding.", "status": "pending", "requested_at": "2026-11-10T07:00:00+00:00", "decided_at": null, "review_url": "https://www.helprange.com/user/outside-team/10/" } ] } ``` ```bash curl -X GET https://www.helprange.com/api/v2/data-rooms/10/access-requests \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Data room signature requests `GET /api/v2/data-rooms/{data_room_id}/signature-requests` Documents of the room sent to guests for signature, with each signer's status. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:read`; reference: https://www.helprange.com/developers/api/list-data-room-signature-requests. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). Responses: 200, 401, 402, 403, 404, 405, 429. Example (200): ```json { "data_room": "Project Falcon", "signature_requests": [ { "envelope_id": "9f1c2e7a4b", "document": "spa.pdf", "document_id": 260, "status": "sent", "summary": "1 of 2 signed", "sent_at": "2026-11-10T08:00:02+00:00", "due": null, "signers": [ { "email": "buyer@bidco.com", "status": "signed", "signed_at": "2026-11-10T09:00:00+00:00" } ] } ] } ``` ```bash curl -X GET https://www.helprange.com/api/v2/data-rooms/10/signature-requests \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### List Q&A questions `GET /api/v2/data-rooms/{data_room_id}/questions` Questions of the room's Q&A, newest first. Q&A and request lists need the Max or Unlimited plan, the Deal Room Pass or the trial (else 402). Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:read`; reference: https://www.helprange.com/developers/api/list-data-room-questions. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). - `status` (query, "all" | "open" | "published" | "new" | "assigned" | "answered" | "approved" | "declined"): Default all. answered = waiting for approval. - `category` (query, "legal" | "financial" | "tax" | "hr" | "commercial" | "it" | "other"): Only this category. - `limit` (query, integer): 1–500 (default 100). Responses: 200, 400, 401, 402, 403, 404, 405, 429. Example (200): ```json { "data_room_id": 10, "questions": [ { "id": 41, "number": "Q7", "status": "assigned", "status_label": "Assigned", "category": "legal", "text": "Any pending litigation?", "source": "guest", "asked_by": "buyer@bidco.com", "document_id": 252, "document": "articles.pdf", "index": "2.1", "page": 4, "visibility": "private", "request_item_id": null, "assignee": "ann@acme.com", "answer": null, "answer_approved": false, "decline_reason": null, "asked_at": "2026-11-10T07:30:00+00:00", "answered_at": null, "approved_at": null } ] } ``` ```bash curl -X GET 'https://www.helprange.com/api/v2/data-rooms/10/questions?status=open' \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Add a Q&A question `POST /api/v2/data-rooms/{data_room_id}/questions` Adds a question (e.g. from a buyer's list), like importing a question list in the panel: nobody is e-mailed. Answer and approve it in the panel. 409 when the room is frozen. Q&A and request lists need the Max or Unlimited plan, the Deal Room Pass or the trial (else 402). Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:write`; reference: https://www.helprange.com/developers/api/add-data-room-question. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). Body: - `text` (string, required): The question. (max length 4000) - `category` ("legal" | "financial" | "tax" | "hr" | "commercial" | "it" | "other"): Default other. - `document_id` (integer): A document of the room the question is about … - `index` (string): … or its index number, e.g. "2.1". - `page` (integer): Page number. (min 1) - `all_guests` (boolean): true: published to the room's FAQ once answered. Responses: 201, 400, 401, 402, 403, 404, 405, 409, 429. Example (201): ```json { "id": 42, "number": "Q8", "status": "new", "status_label": "New", "category": "legal", "text": "Any pending litigation?", "source": "import", "asked_by": null, "document_id": 252, "document": "articles.pdf", "index": "2.1", "page": 4, "visibility": "private", "request_item_id": null, "assignee": null, "answer": null, "answer_approved": false, "decline_reason": null, "asked_at": "2026-11-10T07:30:00+00:00", "answered_at": null, "approved_at": null } ``` ```bash curl -X POST https://www.helprange.com/api/v2/data-rooms/10/questions \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"text": "Any pending litigation?", "category": "legal", "index": "2.1", "page": 4}' ``` ### List due diligence request items `GET /api/v2/data-rooms/{data_room_id}/requests` The room's request list. Q&A and request lists need the Max or Unlimited plan, the Deal Room Pass or the trial (else 402). Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:read`; reference: https://www.helprange.com/developers/api/list-data-room-request-items. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). - `status` (query, "open" | "in_progress" | "provided" | "na"): Only this status. Responses: 200, 400, 401, 402, 403, 404, 405, 429. Example (200): ```json { "data_room_id": 10, "request_items": [ { "id": 3, "number": "1.1", "category": "Legal", "description": "Articles of association", "priority": "high", "due_date": "2026-12-01", "assignee": "ann@acme.com", "status": "provided", "status_label": "Provided", "documents": [ { "document_id": 252, "index": "2.1" } ], "created_at": "2026-11-10T07:00:00+00:00", "updated_at": "2026-11-10T07:10:00+00:00" } ] } ``` ```bash curl -X GET https://www.helprange.com/api/v2/data-rooms/10/requests \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Add a request list item `POST /api/v2/data-rooms/{data_room_id}/requests` Adds an item to the room's due diligence request list. 409 when the room is frozen. Q&A and request lists need the Max or Unlimited plan, the Deal Room Pass or the trial (else 402). Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:write`; reference: https://www.helprange.com/developers/api/add-data-room-request-item. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). Body: - `description` (string, required): What is requested. - `number` (string): Your numbering, e.g. "1.1". - `category` (string): Free text, e.g. "Legal". - `priority` ("high" | "medium" | "low"): Default medium. - `due_date` (string (date)): YYYY-MM-DD. - `assignee` (string): E-mail of a team member who manages the room. - `status` ("open" | "in_progress" | "provided" | "na"): Default open. - `documents` (array of string or integer): Documents that answer it: index numbers as strings ("2.1") and / or document ids as numbers. Responses: 201, 400, 401, 402, 403, 404, 405, 409, 429. Example (201): ```json { "id": 3, "number": "1.1", "category": "Legal", "description": "Articles of association", "priority": "high", "due_date": "2026-12-01", "assignee": "ann@acme.com", "status": "open", "status_label": "Open", "documents": [ { "document_id": 252, "index": "2.1" } ], "created_at": "2026-11-10T07:00:00+00:00", "updated_at": "2026-11-10T07:10:00+00:00" } ``` ```bash curl -X POST https://www.helprange.com/api/v2/data-rooms/10/requests \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"description": "Articles of association", "number": "1.1", "priority": "high", "due_date": "2026-12-01", "documents": ["2.1"]}' ``` ### List data room folders `GET /api/v2/data-rooms/{data_room_id}/folders` Folders of a data room with their document counts. A room folder groups the room's documents (one level); the files stay in the folders they come from. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:read`; reference: https://www.helprange.com/developers/api/list-data-room-folders. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). Responses: 200, 401, 402, 403, 404, 405, 429. Example (200): ```json { "data_room_id": 10, "data_room": "Project Falcon", "frozen": false, "folders_enabled": true, "folders": [ { "id": 5, "name": "Financials", "position": 0, "data_room_id": 10, "documents_count": 3 } ] } ``` ```bash curl -X GET https://www.helprange.com/api/v2/data-rooms/10/folders \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Create a data room folder `POST /api/v2/data-rooms/{data_room_id}/folders` Creates a folder in a data room. Refused with 400 while folders are off for the room; 409 when the room is frozen. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:write`; reference: https://www.helprange.com/developers/api/create-data-room-folder. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). Body: - `name` (string, required): Folder name. (max length 100) Responses: 201, 400, 401, 402, 403, 404, 405, 409, 429. Example (201): ```json { "id": 5, "name": "Financials", "position": 0, "data_room_id": 10, "documents_count": 0 } ``` ```bash curl -X POST https://www.helprange.com/api/v2/data-rooms/10/folders \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"name": "Financials"}' ``` ### Move data room documents `POST /api/v2/data-rooms/{data_room_id}/folders/move` Moves documents of the room into one of its folders, or to the room's top level (folder_id null or missing). Ids not in the room are listed in not_in_room. 409 when the room is frozen. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:write`; reference: https://www.helprange.com/developers/api/move-data-room-documents. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). Body: - `room_link_ids` (array of integer): Room documents (room_link_id from the room's documents or the index) … - `document_ids` (array of integer): … and / or document ids. - `folder_id` (integer or null): A folder of this room; null = the top level. Responses: 200, 400, 401, 402, 403, 404, 405, 409, 429. Example (200): ```json { "moved": 2, "data_room_id": 10, "room_folder_id": 5, "room_folder": "Financials", "not_in_room": [] } ``` ```bash curl -X POST https://www.helprange.com/api/v2/data-rooms/10/folders/move \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"room_link_ids": [910, 911], "folder_id": 5}' ``` ### Rename a data room folder `POST /api/v2/data-rooms/{data_room_id}/folders/{folder_id}` Renames a folder of a data room. 409 when the room is frozen. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:write`; reference: https://www.helprange.com/developers/api/rename-data-room-folder. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). - `folder_id` (path, integer, required): Data room folder id (from List data room folders). Body: - `name` (string, required): New name. (max length 100) Responses: 200, 400, 401, 402, 403, 404, 405, 409, 429. Example (200): ```json { "id": 5, "name": "Finance", "position": 0, "data_room_id": 10 } ``` ```bash curl -X POST https://www.helprange.com/api/v2/data-rooms/10/folders/5 \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"name": "Finance"}' ``` ### Delete a data room folder `POST /api/v2/data-rooms/{data_room_id}/folders/{folder_id}/delete` Deletes the room folder only – its documents stay in the room, at the top level. 409 when the room is frozen. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:write`; reference: https://www.helprange.com/developers/api/delete-data-room-folder. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). - `folder_id` (path, integer, required): Data room folder id (from List data room folders). Responses: 200, 400, 401, 402, 403, 404, 405, 409, 429. Example (200): ```json { "deleted": true, "id": 5, "data_room_id": 10, "moved_to_top": 3 } ``` ```bash curl -X POST https://www.helprange.com/api/v2/data-rooms/10/folders/5/delete \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### List data room documents `GET /api/v2/data-rooms/{data_room_id}/documents` The room's documents in index order, with their room folder and the folder (and subfolder) they come from; group is the folder guests see the document under. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:read`; reference: https://www.helprange.com/developers/api/list-data-room-documents. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). Responses: 200, 401, 402, 403, 404, 405, 429. Example (200): ```json { "data_room_id": 10, "documents": [ { "room_link_id": 910, "document_id": 260, "name": "pnl.xlsx", "number": "1.1", "group": "Financials", "room_folder_id": 5, "room_folder": "Financials", "folder_id": 44, "folder": "Finance", "subfolder_id": 9, "subfolder": "2026", "path": "Finance › 2026", "approval": "published" } ] } ``` ```bash curl -X GET https://www.helprange.com/api/v2/data-rooms/10/documents \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Add documents to a data room `POST /api/v2/data-rooms/{data_room_id}/documents` Like "Add existing documents" on the room page: documents from folders you can open, optionally straight into a room folder. The files stay in their folders. When the room sends e-mail notifications, its guests get one e-mail for the batch (at most 50 recipients per request, else 400 TOO_MANY_RECIPIENTS). 409 when the room is frozen. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:write`; reference: https://www.helprange.com/developers/api/add-data-room-documents. Parameters: - `data_room_id` (path, integer, required): Data room id (from List data rooms). Body: - `document_ids` (array of integer, required): Documents to add (up to 100). - `folder_id` (integer): A folder of this room (optional). Responses: 201, 400, 401, 402, 403, 404, 405, 409, 429. Example (201): ```json { "added": 2, "data_room_id": 10, "room_folder_id": 5, "room_folder": "Financials", "documents": [ { "room_link_id": 912, "document_id": 260 }, { "room_link_id": 913, "document_id": 261 } ] } ``` ```bash curl -X POST https://www.helprange.com/api/v2/data-rooms/10/documents \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"document_ids": [260, 261], "folder_id": 5}' ``` ## E-signature Send documents for e-signature, follow them and download the signed PDF. ### List e-signature requests `GET /api/v2/signature-requests` E-signature requests – administrators see the whole team, members the ones they created or sent – newest first. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:read`; reference: https://www.helprange.com/developers/api/list-signature-requests. Parameters: - `status` (query, "draft" | "sent" | "expired" | "completed" | "declined" | "voided"): Only this status. - `query` (query, string): Words in the title or a recipient's name / e-mail. - `limit` (query, integer): 1–200 (default 50). Responses: 200, 401, 402, 403, 405, 429. Example (200): ```json { "signature_requests": [ { "id": "9f1c2e7a4b", "title": "Mutual NDA", "status": "sent", "status_label": "Sent", "sender": "Ann Admin", "document_id": 250, "data_room": null, "created_at": "2026-11-10T08:00:00+00:00", "sent_at": "2026-11-10T08:00:02+00:00", "expires_at": "2026-12-10T22:59:59+00:00", "completed_at": null, "ended_at": null, "signed": 0, "recipients_count": 2, "url": "https://www.helprange.com/user/signature-doc?signatureId=9f1c2e7a4b" } ] } ``` ```bash curl -X GET 'https://www.helprange.com/api/v2/signature-requests?status=sent' \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Send a document for e-signature `POST /api/v2/signature-requests` Sends a document from a folder you can open for e-signature (plans with e-signature). A signature page with a block per signer is added at the end; every recipient gets an e-mail with a signing link (counted in the team's API e-mail budget). Form posts can pass repeated signer_email fields instead of signers. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:write`; reference: https://www.helprange.com/developers/api/create-signature-request. Body: - `document_id` (integer, required): A document in a folder you can open. - `signers` (array of object, required): Recipients in signing order. (min items 1, max items 20) - `title` (string): Default: the file name. - `message` (string): Shown in the e-mail. - `sequential` (boolean): true: sign one after another. - `expires_on` (string (date)): YYYY-MM-DD (default: 30 days). Responses: 201, 400, 401, 402, 403, 404, 405, 429. Example (201): ```json { "id": "9f1c2e7a4b", "title": "Mutual NDA", "status": "sent", "status_label": "Sent", "sender": "Ann Admin", "document_id": 250, "data_room": null, "created_at": "2026-11-10T08:00:00+00:00", "sent_at": "2026-11-10T08:00:02+00:00", "expires_at": "2026-12-10T22:59:59+00:00", "completed_at": null, "ended_at": null, "signed": 0, "recipients_count": 2, "url": "https://www.helprange.com/user/signature-doc?signatureId=9f1c2e7a4b", "sent": true } ``` ```bash curl -X POST https://www.helprange.com/api/v2/signature-requests \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"document_id": 250, "title": "Mutual NDA", "signers": [{"name": "Anna Client", "email": "anna@client.com"}, {"name": "Legal", "email": "legal@client.com", "action": "review"}], "sequential": true}' ``` ### E-signature request details `GET /api/v2/signature-requests/{envelope_id}` One request with each recipient's status and the audit trail. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:read`; reference: https://www.helprange.com/developers/api/get-signature-request. Parameters: - `envelope_id` (path, string, required): E-signature request id (from List e-signature requests). Responses: 200, 401, 402, 403, 404, 405, 429. Example (200): ```json { "id": "9f1c2e7a4b", "title": "Mutual NDA", "status": "sent", "status_label": "Sent", "sender": "Ann Admin", "document_id": 250, "data_room": null, "created_at": "2026-11-10T08:00:00+00:00", "sent_at": "2026-11-10T08:00:02+00:00", "expires_at": "2026-12-10T22:59:59+00:00", "completed_at": null, "ended_at": null, "signed": 0, "recipients_count": 2, "url": "https://www.helprange.com/user/signature-doc?signatureId=9f1c2e7a4b", "message": "", "sequential": true, "recipients": [ { "name": "Anna Client", "email": "anna@client.com", "role": "sign", "order": 1, "status": "viewed", "status_label": "Viewed", "can_act_now": true, "completed_at": null } ], "events": [ { "at": "2026-11-10T08:00:00+00:00", "kind": "created", "label": "Created", "recipient": null }, { "at": "2026-11-10T08:05:13+00:00", "kind": "opened", "label": "Opened", "recipient": "anna@client.com" } ], "signed_pdf_available": false } ``` ```bash curl -X GET https://www.helprange.com/api/v2/signature-requests/9f1c2e7a4b \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Remind signers `POST /api/v2/signature-requests/{envelope_id}/remind` E-mails a reminder to the recipients who still have to act – at most once an hour per request (400 otherwise). Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:write`; reference: https://www.helprange.com/developers/api/remind-signature-request. Parameters: - `envelope_id` (path, string, required): E-signature request id (from List e-signature requests). Responses: 200, 400, 401, 402, 403, 404, 405, 429. Example (200): ```json { "id": "9f1c2e7a4b", "reminded": [ "anna@client.com" ] } ``` ```bash curl -X POST https://www.helprange.com/api/v2/signature-requests/9f1c2e7a4b/remind \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Void an e-signature request `POST /api/v2/signature-requests/{envelope_id}/void` Cancels a sent request: nobody can sign it any more and the waiting recipients are told. Cannot be undone. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:write`; reference: https://www.helprange.com/developers/api/void-signature-request. Parameters: - `envelope_id` (path, string, required): E-signature request id (from List e-signature requests). Body: - `reason` (string): Optional, shown to the waiting recipients. Responses: 200, 400, 401, 402, 403, 404, 405, 429. Example (200): ```json { "id": "9f1c2e7a4b", "title": "Mutual NDA", "status": "voided", "status_label": "Voided", "sender": "Ann Admin", "document_id": 250, "data_room": null, "created_at": "2026-11-10T08:00:00+00:00", "sent_at": "2026-11-10T08:00:02+00:00", "expires_at": "2026-12-10T22:59:59+00:00", "completed_at": null, "ended_at": "2026-11-11T10:00:00+00:00", "signed": 0, "recipients_count": 2, "url": "https://www.helprange.com/user/signature-doc?signatureId=9f1c2e7a4b" } ``` ```bash curl -X POST https://www.helprange.com/api/v2/signature-requests/9f1c2e7a4b/void \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"reason": "Wrong version"}' ``` ### Signed PDF link `GET /api/v2/signature-requests/{envelope_id}/signed-pdf` Download link of the signed PDF with the certificate of completion. 409 while not everyone has signed. Requests signed before 2026-11-10 answer with a panel link and a note instead. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:read`; reference: https://www.helprange.com/developers/api/get-signed-pdf. Parameters: - `envelope_id` (path, string, required): E-signature request id (from List e-signature requests). Responses: 200, 401, 402, 403, 404, 405, 409, 429. Example (200): ```json { "id": "9f1c2e7a4b", "url": "https://www.helprange.com/esign/download/eyJ0.../", "valid_days": 30, "sha256": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8", "sealed": true } ``` ```bash curl -X GET https://www.helprange.com/api/v2/signature-requests/9f1c2e7a4b/signed-pdf \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ## Offers Offers with their own link; the API never e-mails the client. ### List offers `GET /api/v2/offers` Offers you can see (administrators: the whole team), newest first, with paging. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `links:read`; reference: https://www.helprange.com/developers/api/list-offers. Parameters: - `status` (query, "open" | "all" | "active" | "expired" | "accepted" | "declined" | "draft" | "closed"): Default open (everything but archived). - `search` (query, string): Words in the title, the client's name, company or e-mail (q works too). - `limit` (query, integer): 1–200 (default 50). - `page` (query, integer): From 1. Responses: 200, 400, 401, 402, 403, 405, 429. Example (200): ```json { "offers": [ { "id": 31, "title": "Fit-out Q1", "status": "active", "expired": false, "client": { "first_name": "Anna", "last_name": "Nowak", "company": "Acme", "email": "anna@acme.com" }, "kind": "files", "url": "https://www.helprange.com/o/Xk2pQ9/", "document_views": 4, "page_visits": 2, "last_viewed_at": "2026-11-11T09:12:00+00:00", "sent_at": null, "created_at": "2026-11-11T08:00:00+00:00", "updated_at": "2026-11-11T08:00:00+00:00" } ], "page": 1, "limit": 50, "total": 1, "next_page": null } ``` ```bash curl -X GET 'https://www.helprange.com/api/v2/offers?status=active&search=acme' \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Create an offer `POST /api/v2/offers` Creates an offer with its own link: based on a folder (folder_id – new files show up), from documents (file_ids), or empty with just a title and client. Offers without a folder get their own folder "Offer - ". The API never e-mails the client: send the returned url yourself. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `links:write`; reference: https://www.helprange.com/developers/api/create-offer. Body: - `folder_id` (integer): Base the offer on this folder. - `file_ids` (array of integer): Documents (a JSON list, repeated form fields or "1,2"). - `status` ("active" | "draft"): Default active. - `title` (string): What the client sees as the title. Create: default the folder's / first document's name. - `client_first_name` (string) - `client_last_name` (string) - `client_company` (string) - `client_email` (string): The client's e-mail – never e-mailed by the API. - `message` (string): Message on the offer page. (max length 2000) - `price_summary` (string): One line, e.g. "3 packages, from 2,400 EUR". - `show_accept` (boolean): "Accept offer" button (default on). - `accept_label` (string) - `show_contact` (boolean): "Request contact" button (default on). - `contact_label` (string) - `show_decline` (boolean): "Decline" button with an optional reason (default off). - `decline_label` (string) - `require_email_verification` (boolean): The client verifies their e-mail with a one-time code (default on). - `access` ("any" | "email" | "domain"): With e-mail verification: anyone, only client_email, or client_email and colleagues at the same domain. - `allow_download` (boolean) - `watermark` (boolean) - `expires_on` (string): Last day, YYYY-MM-DD ("" removes it). - `passcode` (string): Asked before the offer opens ("" removes it). - `screenshot_protection` (boolean): Pro and higher (else 402). - `one_viewer_at_a_time` (boolean) - `open_directly` (boolean): One-file offers: open the document directly instead of the offer page. - `layout` ("classic" | "split" | "cover" | "minimal") Responses: 201, 400, 401, 402, 403, 404, 405, 429. Example (201): ```json { "id": 31, "title": "Fit-out Q1", "status": "active", "expired": false, "client": { "first_name": "Anna", "last_name": "Nowak", "company": "Acme", "email": "anna@acme.com" }, "kind": "files", "url": "https://www.helprange.com/o/Xk2pQ9/", "document_views": 4, "page_visits": 2, "last_viewed_at": "2026-11-11T09:12:00+00:00", "sent_at": null, "created_at": "2026-11-11T08:00:00+00:00", "updated_at": "2026-11-11T08:00:00+00:00", "stored_status": "active", "public_url": "https://www.helprange.com/o/Xk2pQ9/", "panel_url": "https://www.helprange.com/user/offers/31/", "message": "Hi Anna, here is our proposal.", "price_summary": "from 2,400 EUR", "buttons": { "accept": { "shown": true, "label": "" }, "contact": { "shown": true, "label": "" }, "decline": { "shown": true, "label": "" } }, "protection": { "require_email_verification": true, "access": "domain", "allowed_emails": "anna@acme.com @acme.com", "allow_download": false, "watermark": false, "passcode": true, "screenshot_protection": false, "one_viewer_at_a_time": false, "expires_on": "2026-12-31" }, "open_directly": false, "layout": "classic", "theme": "light", "base_folder": null, "own_folder": { "id": 77, "name": "Offer - Fit-out Q1" }, "files": [ { "id": 250, "name": "proposal.pdf", "folder_id": 77, "source": "own_folder", "view_url": "https://www.helprange.com/view/5b0c.../proposal.pdf", "views": 3 } ], "people": 1, "engagement": 82, "responses_count": 1, "responses": [ { "kind": "declined the offer", "type": "decline", "offer_id": 31, "email": "anna@acme.com", "email_verified": true, "offer": "Fit-out Q1", "comment": "Too expensive", "document_id": null, "engagement": 64, "country": "PL", "at": "2026-11-11T10:00:00+00:00" } ], "sent_to": null, "created_by": "ann@acme.com", "legacy_url": null, "send_by_email": "HelpRange does not e-mail offers through the API or MCP. Send the offer link (url) yourself, or use \"Send by e-mail\" on the offer page in HelpRange." } ``` ```bash curl -X POST https://www.helprange.com/api/v2/offers \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"title": "Fit-out Q1", "file_ids": [250, 251], "client_company": "Acme", "client_email": "anna@acme.com", "access": "domain", "show_decline": true, "passcode": "oak-41", "expires_on": "2026-12-31"}' ``` ### Offer details `GET /api/v2/offers/{offer_id}` Texts, client, buttons, protection, layout, its documents (each with the offer's own link and views), visits, engagement and the 10 latest responses. status is what the panel shows (expired for an active offer past its date); stored_status the saved one. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `links:read`; reference: https://www.helprange.com/developers/api/get-offer. Parameters: - `offer_id` (path, integer, required): Offer id (from List offers). Responses: 200, 401, 402, 403, 404, 405, 429. Example (200): ```json { "id": 31, "title": "Fit-out Q1", "status": "active", "expired": false, "client": { "first_name": "Anna", "last_name": "Nowak", "company": "Acme", "email": "anna@acme.com" }, "kind": "files", "url": "https://www.helprange.com/o/Xk2pQ9/", "document_views": 4, "page_visits": 2, "last_viewed_at": "2026-11-11T09:12:00+00:00", "sent_at": null, "created_at": "2026-11-11T08:00:00+00:00", "updated_at": "2026-11-11T08:00:00+00:00", "stored_status": "active", "public_url": "https://www.helprange.com/o/Xk2pQ9/", "panel_url": "https://www.helprange.com/user/offers/31/", "message": "Hi Anna, here is our proposal.", "price_summary": "from 2,400 EUR", "buttons": { "accept": { "shown": true, "label": "" }, "contact": { "shown": true, "label": "" }, "decline": { "shown": true, "label": "" } }, "protection": { "require_email_verification": true, "access": "domain", "allowed_emails": "anna@acme.com @acme.com", "allow_download": false, "watermark": false, "passcode": true, "screenshot_protection": false, "one_viewer_at_a_time": false, "expires_on": "2026-12-31" }, "open_directly": false, "layout": "classic", "theme": "light", "base_folder": null, "own_folder": { "id": 77, "name": "Offer - Fit-out Q1" }, "files": [ { "id": 250, "name": "proposal.pdf", "folder_id": 77, "source": "own_folder", "view_url": "https://www.helprange.com/view/5b0c.../proposal.pdf", "views": 3 } ], "people": 1, "engagement": 82, "responses_count": 1, "responses": [ { "kind": "declined the offer", "type": "decline", "offer_id": 31, "email": "anna@acme.com", "email_verified": true, "offer": "Fit-out Q1", "comment": "Too expensive", "document_id": null, "engagement": 64, "country": "PL", "at": "2026-11-11T10:00:00+00:00" } ], "sent_to": null, "created_by": "ann@acme.com", "legacy_url": null, "send_by_email": "HelpRange does not e-mail offers through the API or MCP. Send the offer link (url) yourself, or use \"Send by e-mail\" on the offer page in HelpRange." } ``` ```bash curl -X GET https://www.helprange.com/api/v2/offers/31 \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Update an offer `PATCH /api/v2/offers/{offer_id}` Changes only the fields you send. The offer's links follow at once (protection, expiry, status). The API never e-mails the client: send the returned url yourself. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `links:write`; reference: https://www.helprange.com/developers/api/update-offer. Parameters: - `offer_id` (path, integer, required): Offer id (from List offers). Body: - `title` (string): What the client sees as the title. Create: default the folder's / first document's name. - `client_first_name` (string) - `client_last_name` (string) - `client_company` (string) - `client_email` (string): The client's e-mail – never e-mailed by the API. - `message` (string): Message on the offer page. (max length 2000) - `price_summary` (string): One line, e.g. "3 packages, from 2,400 EUR". - `show_accept` (boolean): "Accept offer" button (default on). - `accept_label` (string) - `show_contact` (boolean): "Request contact" button (default on). - `contact_label` (string) - `show_decline` (boolean): "Decline" button with an optional reason (default off). - `decline_label` (string) - `require_email_verification` (boolean): The client verifies their e-mail with a one-time code (default on). - `access` ("any" | "email" | "domain"): With e-mail verification: anyone, only client_email, or client_email and colleagues at the same domain. - `allow_download` (boolean) - `watermark` (boolean) - `expires_on` (string): Last day, YYYY-MM-DD ("" removes it). - `passcode` (string): Asked before the offer opens ("" removes it). - `screenshot_protection` (boolean): Pro and higher (else 402). - `one_viewer_at_a_time` (boolean) - `open_directly` (boolean): One-file offers: open the document directly instead of the offer page. - `layout` ("classic" | "split" | "cover" | "minimal") - `status` ("active" | "draft" | "accepted" | "declined" | "closed"): active also reopens an accepted, declined or archived offer; closed = archived. Responses: 200, 400, 401, 402, 403, 404, 405, 429. Example (200): ```json { "id": 31, "title": "Fit-out Q1", "status": "active", "expired": false, "client": { "first_name": "Anna", "last_name": "Nowak", "company": "Acme", "email": "anna@acme.com" }, "kind": "files", "url": "https://www.helprange.com/o/Xk2pQ9/", "document_views": 4, "page_visits": 2, "last_viewed_at": "2026-11-11T09:12:00+00:00", "sent_at": null, "created_at": "2026-11-11T08:00:00+00:00", "updated_at": "2026-11-11T08:00:00+00:00", "stored_status": "active", "public_url": "https://www.helprange.com/o/Xk2pQ9/", "panel_url": "https://www.helprange.com/user/offers/31/", "message": "Hi Anna, here is our proposal.", "price_summary": "from 2,400 EUR", "buttons": { "accept": { "shown": true, "label": "" }, "contact": { "shown": true, "label": "" }, "decline": { "shown": true, "label": "" } }, "protection": { "require_email_verification": true, "access": "domain", "allowed_emails": "anna@acme.com @acme.com", "allow_download": false, "watermark": false, "passcode": true, "screenshot_protection": false, "one_viewer_at_a_time": false, "expires_on": "2026-12-31" }, "open_directly": false, "layout": "classic", "theme": "light", "base_folder": null, "own_folder": { "id": 77, "name": "Offer - Fit-out Q1" }, "files": [ { "id": 250, "name": "proposal.pdf", "folder_id": 77, "source": "own_folder", "view_url": "https://www.helprange.com/view/5b0c.../proposal.pdf", "views": 3 } ], "people": 1, "engagement": 82, "responses_count": 1, "responses": [ { "kind": "declined the offer", "type": "decline", "offer_id": 31, "email": "anna@acme.com", "email_verified": true, "offer": "Fit-out Q1", "comment": "Too expensive", "document_id": null, "engagement": 64, "country": "PL", "at": "2026-11-11T10:00:00+00:00" } ], "sent_to": null, "created_by": "ann@acme.com", "legacy_url": null, "send_by_email": "HelpRange does not e-mail offers through the API or MCP. Send the offer link (url) yourself, or use \"Send by e-mail\" on the offer page in HelpRange." } ``` ```bash curl -X PATCH https://www.helprange.com/api/v2/offers/31 \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"status": "active", "expires_on": "2027-01-31", "one_viewer_at_a_time": true}' ``` ### Update an offer (POST) `POST /api/v2/offers/{offer_id}` Changes only the fields you send. The offer's links follow at once (protection, expiry, status). POST does the same as PATCH, for tools that cannot send PATCH. The API never e-mails the client: send the returned url yourself. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `links:write`; reference: https://www.helprange.com/developers/api/update-offer-post. Parameters: - `offer_id` (path, integer, required): Offer id (from List offers). Body: - `title` (string): What the client sees as the title. Create: default the folder's / first document's name. - `client_first_name` (string) - `client_last_name` (string) - `client_company` (string) - `client_email` (string): The client's e-mail – never e-mailed by the API. - `message` (string): Message on the offer page. (max length 2000) - `price_summary` (string): One line, e.g. "3 packages, from 2,400 EUR". - `show_accept` (boolean): "Accept offer" button (default on). - `accept_label` (string) - `show_contact` (boolean): "Request contact" button (default on). - `contact_label` (string) - `show_decline` (boolean): "Decline" button with an optional reason (default off). - `decline_label` (string) - `require_email_verification` (boolean): The client verifies their e-mail with a one-time code (default on). - `access` ("any" | "email" | "domain"): With e-mail verification: anyone, only client_email, or client_email and colleagues at the same domain. - `allow_download` (boolean) - `watermark` (boolean) - `expires_on` (string): Last day, YYYY-MM-DD ("" removes it). - `passcode` (string): Asked before the offer opens ("" removes it). - `screenshot_protection` (boolean): Pro and higher (else 402). - `one_viewer_at_a_time` (boolean) - `open_directly` (boolean): One-file offers: open the document directly instead of the offer page. - `layout` ("classic" | "split" | "cover" | "minimal") - `status` ("active" | "draft" | "accepted" | "declined" | "closed"): active also reopens an accepted, declined or archived offer; closed = archived. Responses: 200, 400, 401, 402, 403, 404, 405, 429. Example (200): ```json { "id": 31, "title": "Fit-out Q1", "status": "active", "expired": false, "client": { "first_name": "Anna", "last_name": "Nowak", "company": "Acme", "email": "anna@acme.com" }, "kind": "files", "url": "https://www.helprange.com/o/Xk2pQ9/", "document_views": 4, "page_visits": 2, "last_viewed_at": "2026-11-11T09:12:00+00:00", "sent_at": null, "created_at": "2026-11-11T08:00:00+00:00", "updated_at": "2026-11-11T08:00:00+00:00", "stored_status": "active", "public_url": "https://www.helprange.com/o/Xk2pQ9/", "panel_url": "https://www.helprange.com/user/offers/31/", "message": "Hi Anna, here is our proposal.", "price_summary": "from 2,400 EUR", "buttons": { "accept": { "shown": true, "label": "" }, "contact": { "shown": true, "label": "" }, "decline": { "shown": true, "label": "" } }, "protection": { "require_email_verification": true, "access": "domain", "allowed_emails": "anna@acme.com @acme.com", "allow_download": false, "watermark": false, "passcode": true, "screenshot_protection": false, "one_viewer_at_a_time": false, "expires_on": "2026-12-31" }, "open_directly": false, "layout": "classic", "theme": "light", "base_folder": null, "own_folder": { "id": 77, "name": "Offer - Fit-out Q1" }, "files": [ { "id": 250, "name": "proposal.pdf", "folder_id": 77, "source": "own_folder", "view_url": "https://www.helprange.com/view/5b0c.../proposal.pdf", "views": 3 } ], "people": 1, "engagement": 82, "responses_count": 1, "responses": [ { "kind": "declined the offer", "type": "decline", "offer_id": 31, "email": "anna@acme.com", "email_verified": true, "offer": "Fit-out Q1", "comment": "Too expensive", "document_id": null, "engagement": 64, "country": "PL", "at": "2026-11-11T10:00:00+00:00" } ], "sent_to": null, "created_by": "ann@acme.com", "legacy_url": null, "send_by_email": "HelpRange does not e-mail offers through the API or MCP. Send the offer link (url) yourself, or use \"Send by e-mail\" on the offer page in HelpRange." } ``` ```bash curl -X POST https://www.helprange.com/api/v2/offers/31 \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"status": "active", "expires_on": "2027-01-31", "one_viewer_at_a_time": true}' ``` ### Add documents to an offer `POST /api/v2/offers/{offer_id}/files` Adds documents from folders you can open (up to 50). Answers with the offer. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `links:write`; reference: https://www.helprange.com/developers/api/add-offer-files. Parameters: - `offer_id` (path, integer, required): Offer id (from List offers). Body: - `file_ids` (array of integer, required): Document ids. Responses: 200, 400, 401, 402, 403, 404, 405, 429. Example (200): ```json { "id": 31, "title": "Fit-out Q1", "status": "active", "expired": false, "client": { "first_name": "Anna", "last_name": "Nowak", "company": "Acme", "email": "anna@acme.com" }, "kind": "files", "url": "https://www.helprange.com/o/Xk2pQ9/", "document_views": 4, "page_visits": 2, "last_viewed_at": "2026-11-11T09:12:00+00:00", "sent_at": null, "created_at": "2026-11-11T08:00:00+00:00", "updated_at": "2026-11-11T08:00:00+00:00", "stored_status": "active", "public_url": "https://www.helprange.com/o/Xk2pQ9/", "panel_url": "https://www.helprange.com/user/offers/31/", "message": "Hi Anna, here is our proposal.", "price_summary": "from 2,400 EUR", "buttons": { "accept": { "shown": true, "label": "" }, "contact": { "shown": true, "label": "" }, "decline": { "shown": true, "label": "" } }, "protection": { "require_email_verification": true, "access": "domain", "allowed_emails": "anna@acme.com @acme.com", "allow_download": false, "watermark": false, "passcode": true, "screenshot_protection": false, "one_viewer_at_a_time": false, "expires_on": "2026-12-31" }, "open_directly": false, "layout": "classic", "theme": "light", "base_folder": null, "own_folder": { "id": 77, "name": "Offer - Fit-out Q1" }, "files": [ { "id": 250, "name": "proposal.pdf", "folder_id": 77, "source": "own_folder", "view_url": "https://www.helprange.com/view/5b0c.../proposal.pdf", "views": 3 } ], "people": 1, "engagement": 82, "responses_count": 1, "responses": [ { "kind": "declined the offer", "type": "decline", "offer_id": 31, "email": "anna@acme.com", "email_verified": true, "offer": "Fit-out Q1", "comment": "Too expensive", "document_id": null, "engagement": 64, "country": "PL", "at": "2026-11-11T10:00:00+00:00" } ], "sent_to": null, "created_by": "ann@acme.com", "legacy_url": null, "send_by_email": "HelpRange does not e-mail offers through the API or MCP. Send the offer link (url) yourself, or use \"Send by e-mail\" on the offer page in HelpRange." } ``` ```bash curl -X POST https://www.helprange.com/api/v2/offers/31/files \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"file_ids": [252]}' ``` ### Remove documents from an offer `POST /api/v2/offers/{offer_id}/files/remove` Takes documents out of the offer – the files stay in their folders. Answers with the offer. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `links:write`; reference: https://www.helprange.com/developers/api/remove-offer-files. Parameters: - `offer_id` (path, integer, required): Offer id (from List offers). Body: - `file_ids` (array of integer, required): Document ids. Responses: 200, 400, 401, 402, 403, 404, 405, 429. Example (200): ```json { "id": 31, "title": "Fit-out Q1", "status": "active", "expired": false, "client": { "first_name": "Anna", "last_name": "Nowak", "company": "Acme", "email": "anna@acme.com" }, "kind": "files", "url": "https://www.helprange.com/o/Xk2pQ9/", "document_views": 4, "page_visits": 2, "last_viewed_at": "2026-11-11T09:12:00+00:00", "sent_at": null, "created_at": "2026-11-11T08:00:00+00:00", "updated_at": "2026-11-11T08:00:00+00:00", "stored_status": "active", "public_url": "https://www.helprange.com/o/Xk2pQ9/", "panel_url": "https://www.helprange.com/user/offers/31/", "message": "Hi Anna, here is our proposal.", "price_summary": "from 2,400 EUR", "buttons": { "accept": { "shown": true, "label": "" }, "contact": { "shown": true, "label": "" }, "decline": { "shown": true, "label": "" } }, "protection": { "require_email_verification": true, "access": "domain", "allowed_emails": "anna@acme.com @acme.com", "allow_download": false, "watermark": false, "passcode": true, "screenshot_protection": false, "one_viewer_at_a_time": false, "expires_on": "2026-12-31" }, "open_directly": false, "layout": "classic", "theme": "light", "base_folder": null, "own_folder": { "id": 77, "name": "Offer - Fit-out Q1" }, "files": [ { "id": 250, "name": "proposal.pdf", "folder_id": 77, "source": "own_folder", "view_url": "https://www.helprange.com/view/5b0c.../proposal.pdf", "views": 3 } ], "people": 1, "engagement": 82, "responses_count": 1, "responses": [ { "kind": "declined the offer", "type": "decline", "offer_id": 31, "email": "anna@acme.com", "email_verified": true, "offer": "Fit-out Q1", "comment": "Too expensive", "document_id": null, "engagement": 64, "country": "PL", "at": "2026-11-11T10:00:00+00:00" } ], "sent_to": null, "created_by": "ann@acme.com", "legacy_url": null, "send_by_email": "HelpRange does not e-mail offers through the API or MCP. Send the offer link (url) yourself, or use \"Send by e-mail\" on the offer page in HelpRange." } ``` ```bash curl -X POST https://www.helprange.com/api/v2/offers/31/files/remove \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"file_ids": [252]}' ``` ### Responses to an offer `GET /api/v2/offers/{offer_id}/responses` Every answer to this offer (accept, contact, decline with its reason in comment), newest first. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `analytics:read`; reference: https://www.helprange.com/developers/api/list-responses-of-offer. Parameters: - `offer_id` (path, integer, required): Offer id (from List offers). - `kind` (query, "accept" | "contact" | "decline"): Only this kind of answer. - `days` (query, integer): Answers of the last N days, 1–3650. - `limit` (query, integer): 1–200 (default 50). Responses: 200, 401, 402, 403, 404, 405, 429. Example (200): ```json { "responses": [ { "kind": "declined the offer", "type": "decline", "offer_id": 31, "email": "anna@acme.com", "email_verified": true, "offer": "Fit-out Q1", "comment": "Too expensive", "document_id": null, "engagement": 64, "country": "PL", "at": "2026-11-11T10:00:00+00:00" } ] } ``` ```bash curl -X GET https://www.helprange.com/api/v2/offers/31/responses \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ### Offer responses `GET /api/v2/offer-responses` Clients who accepted an offer, asked to be contacted or declined it, across all offers – the last 90 days unless days is given. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `analytics:read`; reference: https://www.helprange.com/developers/api/list-offer-responses. Parameters: - `kind` (query, "accept" | "contact" | "decline"): Only this kind of answer. - `days` (query, integer): Answers of the last N days, 1–3650. - `limit` (query, integer): 1–200 (default 50). Responses: 200, 401, 402, 403, 405, 429. Example (200): ```json { "responses": [ { "kind": "declined the offer", "type": "decline", "offer_id": 31, "email": "anna@acme.com", "email_verified": true, "offer": "Fit-out Q1", "comment": "Too expensive", "document_id": null, "engagement": 64, "country": "PL", "at": "2026-11-11T10:00:00+00:00" } ] } ``` ```bash curl -X GET https://www.helprange.com/api/v2/offer-responses \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ## Store Store sales and buyer access to product folders. ### Give a buyer access `POST /api/v2/folders/{folder_id}/buyers` Gives a buyer access to a product folder (any shop: Teachable, Thinkific, Shopify …): a personal protected link by e-mail for the folder's access length, the buyer id in the watermark. The buyer gets one e-mail (counted in the team's API e-mail budget). 201 for a new buyer, 200 when they already had access. Also works on Pro. Plans: Pro, Max, Unlimited, Deal Room Pass, trial; scope: `links:write`; reference: https://www.helprange.com/developers/api/grant-buyer-access. Parameters: - `folder_id` (path, integer, required): Folder id (from List folders). Body: - `email` (string, required): The buyer's e-mail. - `buyer_id` (string): Your own buyer / order id, shown in the watermark (optional). Responses: 201, 400, 401, 402, 403, 404, 405, 429. Example (201): ```json { "id": "31-buyer@example.com", "email": "buyer@example.com", "folder_id": 31, "folder": "Market report", "new_buyer": true, "access_ends_at": null } ``` ```bash curl -X POST https://www.helprange.com/api/v2/folders/31/buyers \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"email": "buyer@example.com", "buyer_id": "order-1042"}' ``` ### Remove a buyer's access `POST /api/v2/folders/{folder_id}/buyers/remove` Takes a buyer's access to a product folder away. Also works on Pro. Plans: Pro, Max, Unlimited, Deal Room Pass, trial; scope: `links:write`; reference: https://www.helprange.com/developers/api/revoke-buyer-access. Parameters: - `folder_id` (path, integer, required): Folder id (from List folders). Body: - `email` (string, required): The buyer's e-mail. Responses: 200, 400, 401, 402, 403, 404, 405, 429. Example (200): ```json { "id": "31-buyer@example.com", "email": "buyer@example.com", "folder_id": 31, "folder": "Market report", "removed": true } ``` ```bash curl -X POST https://www.helprange.com/api/v2/folders/31/buyers/remove \ -H "X-API-Key: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"email": "buyer@example.com"}' ``` ### Store products and sales `GET /api/v2/store/sales` Store products with Stripe sales and revenue, and the latest Stripe purchases (members: products of their folders). Plans: Max, Unlimited, Deal Room Pass, trial; scope: `analytics:read`; reference: https://www.helprange.com/developers/api/list-store-sales. Parameters: - `product_id` (query, integer): Only this product. - `days` (query, integer): Purchases of the last N days, 1–3650 (default 90). - `limit` (query, integer): Purchases to return, 1–200 (default 50). Responses: 200, 401, 402, 403, 404, 405, 429. Example (200): ```json { "products": [ { "id": 4, "name": "Market report 2026", "kind": "folder", "price": 49.0, "currency": "EUR", "subscription": false, "published": true, "sales": 18, "revenue": 882.0, "buyers_with_access": 18 } ], "purchases": [ { "product": "Market report 2026", "email": "buyer@x.com", "name": "Bo Buyer", "company": "", "paid": "€49.00", "subscription_active": null, "at": "2026-11-10T11:00:00+00:00" } ] } ``` ```bash curl -X GET https://www.helprange.com/api/v2/store/sales \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ## Lead forms Lead magnet / landing page forms. ### List lead magnet forms `GET /api/v2/landing-templates` Lead magnet / landing page forms of the team; folders are the folders (you can open) that use the form. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `links:read`; reference: https://www.helprange.com/developers/api/list-landing-templates. Responses: 200, 401, 402, 403, 405, 429. Example (200): ```json { "landing_templates": [ { "id": 8, "name": "Whitepaper gate", "title": "Get the 2026 report", "description": "", "fields": [ "email", "company" ], "required_fields": [ "email" ], "email_verification": true, "consent": true, "folders": [ 31 ] } ] } ``` ```bash curl -X GET https://www.helprange.com/api/v2/landing-templates \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ## Activity The team activity feed. ### Team activity `GET /api/v2/activity` The Activity feed, newest first: views, new links, data room events, offers, e-signature, store purchases, uploads and (administrators) team events. Members see events of their own documents. Page back with before=next_before. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `analytics:read`; reference: https://www.helprange.com/developers/api/list-activity. Parameters: - `type` (query, "views" | "sharing" | "rooms" | "offers" | "esign" | "store" | "uploads" | "team"): Only one kind of event ("team": administrators only). - `limit` (query, integer): 1–200 (default 30). - `before` (query, string): Only events before this time: next_before of the previous page. - `member_id` (query, integer): Administrators: only this team member's events. Responses: 200, 400, 401, 402, 403, 405, 429. Example (200): ```json { "events": [ { "at": "2026-11-10T09:12:40+00:00", "type": "esign", "text": "anna@client.com signed \"NDA\"", "detail": "", "url": "https://www.helprange.com/user/signature-doc?signatureId=9f1c2e7a4b" } ], "more": true, "next_before": "2026-11-10T09:12:40.123456+00:00" } ``` ```bash curl -X GET 'https://www.helprange.com/api/v2/activity?type=esign&limit=2' \ -H "X-API-Key: $HELPRANGE_API_KEY" ``` ## v1 (legacy) The original /public-api endpoints. They keep working; new integrations should use REST API v2. ### v1: List directories `GET /public-api/directory` Folders you can open in the panel (administrators: every folder of the team). Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:read`; reference: https://www.helprange.com/developers/api/v1-list-directories. Responses: 200, 401, 402, 403, 429. Example (200): ```json { "directories": [ { "id": 17, "name": "sales_documents" }, { "id": 28, "name": "marketing_documents" } ] } ``` ```bash curl -X GET https://www.helprange.com/public-api/directory \ -H "Authorization: $HELPRANGE_API_KEY" ``` ### v1: Create a directory `POST /public-api/directory` Creates a folder in your team. Both properties must be present; onlyIpAddresses may be null. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:write`; reference: https://www.helprange.com/developers/api/v1-create-directory. Body: - `name` (string, required): Folder name (unique in the team). - `onlyIpAddresses` (string or null, required): Comma-separated IP addresses / CIDR ranges allowed to view, or null. Responses: 200, 400, 401, 402, 403, 429. Example (200): ```json { "id": 260, "link": "/view-directory/8956d1e7b91b47e196a8040b1d4894f8" } ``` ```bash curl -X POST https://www.helprange.com/public-api/directory \ -H "Authorization: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"name": "my directory", "onlyIpAddresses": "34.124.145.56,101.127.144.90"}' ``` ### v1: List files in a directory `GET /public-api/directory/{directory_id}/file` Documents of a folder with their links. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:read`; reference: https://www.helprange.com/developers/api/v1-list-files. Parameters: - `directory_id` (path, integer, required): Folder id. Responses: 200, 401, 402, 403, 429. Example (200): ```json { "files": [ { "id": 250, "name": "marketing_copy.pdf", "file_links": [ { "id": 264, "uuid": "471dcc98874d4d0cb7c3eb1874c4e3db", "link": "/view/471dcc98874d4d0cb7c3eb1874c4e3db", "name": "default" } ] } ] } ``` ```bash curl -X GET https://www.helprange.com/public-api/directory/243/file \ -H "Authorization: $HELPRANGE_API_KEY" ``` ### v1: Upload a file to a directory `POST /public-api/directory/{directory_id}/upload/{filename}` Uploads a file (multipart "file") under the given name. Uploading again with the same name replaces the file. 100 MB per file. A refused upload answers {"result": "failure", "reason": …} – 400 (too large, bad name), 429 (daily upload limit), 500 (our side). Plans: Max, Unlimited, Deal Room Pass, trial; scope: `documents:write`; reference: https://www.helprange.com/developers/api/v1-upload-file. Parameters: - `directory_id` (path, integer, required): Folder id. - `filename` (path, string, required): File name: letters, digits, "-", "_" and ".". Body (multipart/form-data with the file.): - `file` (string, required) Responses: 200, 400, 401, 402, 403, 429, 500. Example (200): ```json { "id": 260, "default_link": "/view/8956d1e7b91b47e196a8040b1d4894f8" } ``` ```bash curl -X POST https://www.helprange.com/public-api/directory/38/upload/proposal.pdf \ -H "Authorization: $HELPRANGE_API_KEY" \ -F 'file=@proposal.pdf' ``` ### v1: File statistics `GET /public-api/file/{file_id}/statistics` Views, average reading time (per page too), engagement score and every visit of a document. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `analytics:read`; reference: https://www.helprange.com/developers/api/v1-file-statistics. Parameters: - `file_id` (path, integer, required): Document id. Responses: 200, 401, 402, 403, 404, 429. Example (200): ```json { "engagement_score": 11.57, "total_views": 39, "avg_time_seconds": 24, "avg_time_per_page_seconds": [ 10, 6, 4, 9, 15 ], "recordings": [ { "time": "2026-10-24 10:34:56", "ip": "134.34.12.56", "country": "US", "referrer_url": "https://www.facebook.com", "read_time_sec": 100 } ] } ``` ```bash curl -X GET https://www.helprange.com/public-api/file/124/statistics \ -H "Authorization: $HELPRANGE_API_KEY" ``` ### v1: Create a link `POST /public-api/file/{file_id}/link` A new labelled link for a document. label, isAllowDownloadingAndPrint, isOnlyOneUserViewing, passcode and expirationDate must be present (passcode and expirationDate may be null). Booleans: true / false, 1 / 0 or "true" / "false". An empty label, an expirationDate in the past or unreadable, or a negative / non-whole viewsLimit answer 400 with {"result": "failure", "reason"}. A protection setting your plan does not include answers 403 with {"result": "failure", "reason", "plan_error", "features"}. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `links:write`; reference: https://www.helprange.com/developers/api/v1-create-link. Parameters: - `file_id` (path, integer, required): Document id. Body: - `label` (string, required): Name of the link (not empty). - `isAllowDownloadingAndPrint` (boolean or integer or string, required): Allow download & print. true / false (also 1 / 0 or "true" / "false"). - `isOnlyOneUserViewing` (boolean or integer or string, required): Only one person can view at the same time. true / false (also 1 / 0 or "true" / "false"). - `passcode` (string or null, required): Only spaces = no passcode. - `expirationDate` (string (date) or null, required): Last day, YYYY-MM-DD: today or later (a past day → 400). - `viewsLimit` (integer or null): 0 or null = no limit; negative → 400. (min 0) - `isForwardable` (boolean or integer or string): Default true. true / false (also 1 / 0 or "true" / "false"). - `isScreenshotMask` (boolean or integer or string): Screenshot protection. true / false (also 1 / 0 or "true" / "false"). - `isScreenAppOnly` (boolean or integer or string): Open only in the app. true / false (also 1 / 0 or "true" / "false"). - `isWatermark` (boolean or integer or string): Watermark. true / false (also 1 / 0 or "true" / "false"). Responses: 200, 400, 401, 402, 403, 404, 429. Example (200): ```json { "id": 261, "link": "/view/8956d1e7b91b47e1a67a8040b1d4894f" } ``` ```bash curl -X POST https://www.helprange.com/public-api/file/124/link \ -H "Authorization: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"label": "someLabel", "isAllowDownloadingAndPrint": true, "isOnlyOneUserViewing": false, "isForwardable": true, "isScreenshotMask": false, "isWatermark": false, "passcode": "abcd", "expirationDate": "2026-12-24", "viewsLimit": 10}' ``` ### v1: Add a guest `POST /public-api/guest` Creates a guest of your team by e-mail (an existing guest of the team with this e-mail is reused). Nobody is e-mailed. Counts towards the daily cap of 1,000 guests. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:write`; reference: https://www.helprange.com/developers/api/v1-create-guest. Body: - `email` (string, required): The guest's e-mail. Responses: 200, 400, 401, 402, 403, 429. Example (200): ```json { "id": 5 } ``` ```bash curl -X POST https://www.helprange.com/public-api/guest \ -H "Authorization: $HELPRANGE_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary '{"email": "john@company.com"}' ``` ### v1: Assign a guest to a data room `POST /public-api/guest/{guest_id}/assign/{data_room_id}` Adds a guest (from v1: Add a guest) to a data room and sends the room invitation e-mail (counted in the API e-mail budget). Already in the room: no new invitation. Plans: Max, Unlimited, Deal Room Pass, trial; scope: `rooms:write`; reference: https://www.helprange.com/developers/api/v1-assign-guest. Parameters: - `guest_id` (path, integer, required): Guest id. - `data_room_id` (path, integer, required): Data room id. Responses: 200, 401, 402, 403, 404, 429. Example (200): ```json { "result": "success", "already_in_room": false } ``` ```bash curl -X POST https://www.helprange.com/public-api/guest/7/assign/10 \ -H "Authorization: $HELPRANGE_API_KEY" ``` ## Webhooks Set the endpoint (https, public address) in Settings → Integrations → Webhook (team administrators, every plan). HelpRange POSTs one JSON event per request: ```json { "id": "evt_3f9c2b7e0d1a4c5b8e6f7a9d0c1b2e3f", "event": "document.opened", "api_version": "2026-11-15", "created_at": "2026-11-15T09:12:03.512345Z", "data": { "document": "Offer 2026", "file_id": 123, "link": "ACME", "link_id": 456, "viewer": "jane@acme.com", "country": "PL", "browser": "Chrome", "os": "Mac OS X", "opening_id": 789 } } ``` Headers: `HelpRange-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>` (two `v1=` for 24 hours after a secret rotation), `HelpRange-Event-Id`, `HelpRange-Event-Type`, `HelpRange-Delivery-Attempt`; legacy `X-HelpRange-Signature` = hex HMAC-SHA256 of the raw body only. Verify: compute the HMAC over the exact raw bytes, compare in constant time with any `v1`, and reject `t` more than 5 minutes from your clock. ```python import hashlib, hmac, time def verify_helprange(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool: parts = [p.split('=', 1) for p in (header or '').split(',') if '=' in p] t = next((v for k, v in parts if k == 't'), None) if t is None or not t.isdigit() or abs(time.time() - int(t)) > tolerance: return False expected = hmac.new(secret.encode(), t.encode() + b'.' + raw_body, hashlib.sha256).hexdigest() return any(k == 'v1' and hmac.compare_digest(v, expected) for k, v in parts) ``` Delivery: a 2xx within 10 seconds counts as delivered; redirects are not followed. Failures are retried after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h, 24 h (9 attempts), then marked failed. At-least-once: deduplicate on `id`. 410 Gone turns the endpoint off; so do 15 failed attempts in a row over at least 24 hours (administrators are e-mailed). The delivery log (Settings → Integrations → Delivery log) keeps 30 days: resend, send a test event (`"test": true`), rotate the secret. Sandbox (test-key) activity sends no webhooks. ### Events - `document.opened` – Someone opened a shared link. `data` example: `{"document": "Offer 2026", "file_id": 123, "link": "ACME", "link_id": 456, "viewer": "jane@acme.com", "country": "PL", "browser": "Chrome", "os": "Mac OS X", "opening_id": 789}` - `file_request.uploaded` – A file came in through a file request. `data` example: `{"request": "Financials 2025", "directory": "Due diligence", "filename": "balance.pdf", "uploader_name": "Jan Kowalski", "uploader_email": "jan@example.com", "upload_id": 42}` - `room.access_requested` – A verified visitor asks to join a data room. `data` example: `{"room": "Project Falcon", "room_id": 7, "request_id": 31, "email": "cfo@buyer.com", "name": "Anna Buyer", "company": "Buyer Ltd", "message": "Please add me.", "review_url": "https://www.helprange.com/user/outside-team/7/#access-requests"}` - `nda.accepted` – A visitor accepted (or signed) the NDA of a link or data room. `data` example: `{"nda": "Mutual NDA", "version": 2, "opened": "Project Falcon", "room_id": 7, "link_id": null, "name": "Anna Buyer", "company": "Buyer Ltd", "email": "cfo@buyer.com", "method": "click", "text_sha256": "9f86d0…", "signed_sha256": "", "sealed": true, "certificate_url": "https://www.helprange.com/nda/certificate/…"}` - `document.acknowledged` – A data room guest confirmed reading a document. `data` example: `{"document": "Policy.pdf", "file_id": 123, "version": "3", "room": "Onboarding", "room_id": 7, "email": "new.hire@acme.com", "response_id": 55}` - `offer.accepted` – A reader accepted an offer. `data` example: `{"document": "Offer 2026", "file_id": 123, "link_id": 456, "folder_id": null, "email": "bob@corp.com", "email_verified": true, "comment": "Let us go ahead.", "engagement": 87, "response_id": 56, "offer_id": 9}` - `offer.declined` – A reader declined an offer. `data` example: `{"document": "Offer 2026", "file_id": 123, "link_id": 456, "folder_id": null, "email": "bob@corp.com", "email_verified": true, "comment": "", "engagement": 40, "response_id": 57, "offer_id": 9}` - `offer.contact_requested` – A reader asked to be contacted about an offer or document. `data` example: `{"document": "Offer 2026", "file_id": 123, "link_id": 456, "folder_id": null, "email": "bob@corp.com", "email_verified": false, "comment": "Call me on Monday.", "engagement": null, "response_id": 58, "offer_id": 9}` ## MCP server URL: `https://www.helprange.com/mcp` – Streamable HTTP transport (JSON-RPC 2.0 over POST, JSON responses, stateless; protocol versions 2025-06-18, 2025-03-26, 2024-11-05). Auth: OAuth 2.1 – authorization code + PKCE (S256), dynamic client registration (`https://www.helprange.com/oauth/register`), metadata at `https://www.helprange.com/.well-known/oauth-protected-resource` and `https://www.helprange.com/.well-known/oauth-authorization-server`. The person signs in to HelpRange and clicks "Allow"; the client acts with their rights in the panel. API keys are not accepted by the MCP server – for unattended server-side agents call the REST API with a team API key. Plans: trial, Pro, Max, Unlimited and the Deal Room Pass. Usage limits are shared with the REST API. Install: Claude Code `claude mcp add --transport http helprange https://www.helprange.com/mcp`; Cursor, VS Code, Claude and others: https://www.helprange.com/developers/ai ### Tools #### `list_folders` – List folders (read-only) Folders the user can access (administrators: every folder of the team), with the number of documents, whether new links allow download / get a watermark (the folder's defaults) and how many of its active links allow download now. #### `list_documents` – List documents (read-only) Documents, most recently viewed first: type, pages, views, downloads. Optional folder_id, subfolder_id and a name filter (query); each document carries its path ("Folder › Subfolder"). Arguments: - `folder_id` (integer) - `query` (string) - `limit` (integer) - `subfolder_id` (integer): Only this subfolder (list_subfolders); 0 = documents in no subfolder #### `get_document_stats` – Document statistics (read-only) Views, downloads, average seconds per page and the latest views (who, when, how long) of one document. Arguments: - `document_id` (integer, required) - `recent_views` (integer) #### `list_recent_views` – Recent views (read-only) Who opened which document recently, across all folders (default: last 30 days). Arguments: - `limit` (integer) - `days` (integer) #### `get_document_text` – Read a document (read-only) Text of a document (PDF, Word, Excel, PowerPoint …) page by page ("[Page n]"), so you can answer questions about it or summarize it. Long documents: read on from next_start_page, or pass a page range. Arguments: - `document_id` (integer, required) - `start_page` (integer) - `end_page` (integer) #### `search` – Search documents (read-only) Find documents by words in their name. Returns ids for fetch. Arguments: - `query` (string, required) #### `fetch` – Fetch a document (read-only) Full text of a document by id (from search), to answer questions about its content. Arguments: - `id` (string, required) #### `list_activity` – Team activity (read-only) Recent activity, newest first – the same feed as the Activity page: views, new links, data room invitations / access requests / NDAs / acknowledgements, offers, e-signature events, store purchases, uploads and (administrators) team events. Members see events of their own documents. Filter by type; administrators can filter by member_id. Page with before=next_before. Arguments: - `type` (string) - `limit` (integer) - `before` (string): Only events before this time (ISO 8601) - `member_id` (integer) #### `get_plan` – Plan and limits (read-only) The team's plan, which features it includes (and which plan each other feature needs), AI requests left, reader views left (Free and trial), the first-week view cap and data rooms left (Pro). #### `list_data_rooms` – List data rooms (read-only) Data rooms of the team with guest and document counts and pending access requests (administrators: every room; members: the rooms they created). #### `get_data_room` – Data room activity (read-only) Guests of a data room with their views, last visit and NDA status, and its documents with view counts. Arguments: - `data_room_id` (integer, required) #### `list_access_requests` – Data room access requests (read-only) People who asked to join a data room (default: pending), with name, company and message. Approve or deny them on the data room page. Arguments: - `data_room_id` (integer) - `status` (string) - `limit` (integer) #### `list_room_signature_requests` – Data room signature requests (read-only) Documents of a data room sent to guests for signature, with each signer's status. Arguments: - `data_room_id` (integer, required) #### `list_signature_requests` – List e-signature requests (read-only) E-signature requests (envelopes) – administrators see the whole team, members their own – with status (draft, sent, expired, completed, declined, voided) and how many recipients signed. Arguments: - `status` (string) - `query` (string): Words in the title or a recipient - `limit` (integer) #### `get_signature_request` – E-signature request details (read-only) One e-signature request: each recipient's status and the audit trail (sent, opened, signed, declined, reminders …). Arguments: - `envelope_id` (string, required): E-signature request id (from list_signature_requests) #### `create_signature_request` – Send a document for e-signature (write) Send a PDF/Office document from the user's folders for e-signature (Pro and higher). A signature page with a block per signer is added at the end; every recipient gets an e-mail with a signing link right away. Arguments: - `document_id` (integer, required) - `signers` (array, required): Recipients in signing order - `title` (string) - `message` (string) - `sequential` (boolean): Sign one after another - `expires_on` (string): YYYY-MM-DD (default: 30 days) #### `remind_signature_request` – Remind signers (write) E-mail a reminder to the recipients who still have to sign (at most once an hour). Arguments: - `envelope_id` (string, required): E-signature request id (from list_signature_requests) #### `void_signature_request` – Void an e-signature request (destructive) Cancel a sent e-signature request: nobody can sign it any more and waiting recipients are told. Cannot be undone. Arguments: - `envelope_id` (string, required): E-signature request id (from list_signature_requests) - `reason` (string) #### `get_signed_pdf_link` – Signed PDF link (read-only) Download link of the signed PDF (with the certificate of completion) of a completed e-signature request. Arguments: - `envelope_id` (string, required): E-signature request id (from list_signature_requests) #### `list_offer_responses` – Offer responses (read-only) Clients who accepted an offer, asked to be contacted or declined it (with the reason), with their comment and engagement. Pass offer_id for one offer's answers (from list_offers); otherwise the last 90 days (days). Arguments: - `kind` (string) - `days` (integer) - `limit` (integer) - `offer_id` (integer) #### `list_store_sales` – Store sales (read-only) Store products with Stripe sales and revenue, and the latest Stripe purchases in the HelpRange store (members: products of their folders). Buyers from other platforms or added by hand: list_store_buyers. Arguments: - `product_id` (integer) - `days` (integer) - `limit` (integer) #### `get_store` – Store overview (read-only) The team's HelpRange Store: created or not, address, Stripe connected, the HelpRange fee, and each product (members: products of their folders) with price, content (folder or file), where buyers come from (HelpRange store / Stripe, webhook platforms such as Kajabi or Gumroad, buyers added by hand), whether it shows in the public store, protection and active / total buyer counts. #### `list_store_buyers` – Store buyers (read-only) Buyers of store products from every source (Stripe purchases, Kajabi, Gumroad, Whop … webhooks, added by hand) with status (active, expired, revoked), source, purchase date, access end, running Stripe subscription and when they last opened their library. Filter by product_id, status (default active; all), source (store, manual, webhook or a platform key such as gumroad) and query (e-mail, name or buyer ID). Arguments: - `product_id` (integer) - `status` (string) - `source` (string) - `query` (string) - `limit` (integer) #### `upload_file` – Upload a file (write) Upload a file into HelpRange (PDF, Word, Excel, PowerPoint, images, video …): pass content_base64 or a public file_url. Goes into folder_id or the default folder; returns the document id and its default share link. Limit: 100 MB per file; prefer file_url for files over a few MB. Arguments: - `filename` (string) - `content_base64` (string) - `file_url` (string) - `folder_id` (integer) #### `create_link` – Create a share link (write) Create a new tracked share link for a document with its own settings (download, passcode, expiry, views limit, watermark, e-mail gate, forwarding). A setting the plan does not include is refused with the plan that has it. Returns the URL to send. Arguments: - `document_id` (integer, required) - `label` (string): Who the link is for, e.g. "ACME – Anna" (shown in statistics) - `allow_download` (boolean): Allow download & print. Default: blocked on Pro and higher, allowed on Free (blocking is a Pro feature) - `passcode` (string): Password the viewer must enter (Pro and higher) - `expires_on` (string): Last day the link works, YYYY-MM-DD (Pro and higher) - `views_limit` (integer): Maximum number of views (0 = no limit) - `watermark` (boolean): Watermark pages with the viewer's e-mail and date (Pro and higher) - `require_email` (boolean): Ask for the viewer's e-mail before opening (Pro and higher) - `disallow_forwarding` (boolean): Only the first person who opens it can use it - `one_viewer_at_a_time` (boolean): Only one person can view at the same time #### `list_links` – List share links (read-only) All share links of a document with their settings. Arguments: - `document_id` (integer, required) #### `list_offers` – List offers (read-only) Offers the user can see (administrators: the whole team) with status (draft, active, accepted, declined, expired, closed = archived), client, link and views. Filter by status (default: open = not archived; all) and search (title, client, company, e-mail); page through with page. Arguments: - `status` (string) - `search` (string) - `limit` (integer) - `page` (integer) #### `get_offer` – Offer details (read-only) One offer: texts, client, buttons, protection, its documents (with each document's link and views), visits, engagement and the latest responses. Arguments: - `offer_id` (integer, required) #### `create_offer` – Create an offer (write) Create an offer with its own link: based on a folder (folder_id – new files show up), from documents (file_ids), or empty with just a title and client. Offers need Pro or higher. It never e-mails the client: it returns the offer link (url) to send yourself – sending by e-mail is done in HelpRange with a confirmation. Arguments: - `folder_id` (integer) - `file_ids` (array): Document ids (from list_documents / search) - `status` (string) - `title` (string): Title the client sees (default: the folder / first document name) - `client_first_name` (string) - `client_last_name` (string) - `client_company` (string) - `client_email` (string): The client's e-mail (never e-mailed by the API) - `message` (string): Message to the client on the offer page (up to 2000 characters) - `price_summary` (string): One line, e.g. "3 packages, from 2,400 EUR" - `show_accept` (boolean): "Accept offer" button (default on) - `accept_label` (string) - `show_contact` (boolean): "Request contact" button (default on) - `contact_label` (string) - `show_decline` (boolean): "Decline" button with an optional reason (default off) - `decline_label` (string) - `require_email_verification` (boolean): The client verifies their e-mail with a one-time code before seeing anything (default on) - `access` (string): With e-mail verification: anyone, only client_email, or client_email and colleagues at the same company domain - `allow_download` (boolean) - `watermark` (boolean) - `expires_on` (string): Last day the offer works, YYYY-MM-DD ("" removes it) - `passcode` (string): Passcode asked before the offer opens ("" removes it) - `screenshot_protection` (boolean): Screenshot protection (Pro and higher) - `one_viewer_at_a_time` (boolean): Only one person can view at the same time - `open_directly` (boolean): One-file offers: open the document directly instead of the offer page (default off) - `layout` (string) #### `update_offer` – Update an offer (destructive) Change an offer's fields (only those given): texts, client, buttons, protection, layout or status – status active reopens an accepted or declined offer. It never e-mails the client: it returns the offer link (url) to send yourself – sending by e-mail is done in HelpRange with a confirmation. Arguments: - `offer_id` (integer, required) - `title` (string): Title the client sees (default: the folder / first document name) - `client_first_name` (string) - `client_last_name` (string) - `client_company` (string) - `client_email` (string): The client's e-mail (never e-mailed by the API) - `message` (string): Message to the client on the offer page (up to 2000 characters) - `price_summary` (string): One line, e.g. "3 packages, from 2,400 EUR" - `show_accept` (boolean): "Accept offer" button (default on) - `accept_label` (string) - `show_contact` (boolean): "Request contact" button (default on) - `contact_label` (string) - `show_decline` (boolean): "Decline" button with an optional reason (default off) - `decline_label` (string) - `require_email_verification` (boolean): The client verifies their e-mail with a one-time code before seeing anything (default on) - `access` (string): With e-mail verification: anyone, only client_email, or client_email and colleagues at the same company domain - `allow_download` (boolean) - `watermark` (boolean) - `expires_on` (string): Last day the offer works, YYYY-MM-DD ("" removes it) - `passcode` (string): Passcode asked before the offer opens ("" removes it) - `screenshot_protection` (boolean): Screenshot protection (Pro and higher) - `one_viewer_at_a_time` (boolean): Only one person can view at the same time - `open_directly` (boolean): One-file offers: open the document directly instead of the offer page (default off) - `layout` (string) - `status` (string): active (also to reopen an accepted / declined offer), draft, accepted, declined, closed (archived) #### `add_offer_files` – Add documents to an offer (write) Add documents from the user's folders to an offer. Arguments: - `offer_id` (integer, required) - `file_ids` (array, required): Document ids (from list_documents / search) #### `remove_offer_files` – Remove documents from an offer (destructive) Take documents out of an offer – the files stay in their folders. Arguments: - `offer_id` (integer, required) - `file_ids` (array, required): Document ids (from list_documents / search) #### `list_subfolders` – List subfolders (read-only) Subfolders of a folder with their document counts. Subfolders only group the folder's documents – the folder's settings (protection, links, members) apply to them unchanged. Arguments: - `folder_id` (integer, required): Folder id (from list_folders) #### `set_subfolders_enabled` – Turn subfolders on or off (write) Turn subfolders on or off for a folder (off by default). Turning them off ungroups the documents (they go back to the top of the folder, nothing is deleted) and deletes the subfolders. Arguments: - `folder_id` (integer, required): Folder id (from list_folders) - `enabled` (boolean, required) #### `create_subfolder` – Create a subfolder (write) Create a subfolder in a folder to group its documents. One level only: a subfolder holds documents, never other subfolders. Subfolders are off by default – turn them on for the folder first (set_subfolders_enabled, or "Use subfolders to group files" in the folder settings). Arguments: - `folder_id` (integer, required): Folder id (from list_folders) - `name` (string, required) #### `rename_subfolder` – Rename a subfolder (destructive) Rename a subfolder of a folder; its documents stay in it. Arguments: - `subfolder_id` (integer, required): Subfolder id (from list_subfolders) - `name` (string, required) #### `delete_subfolder` – Delete a subfolder (destructive) Delete a subfolder. Its documents are NOT deleted – they go back to the top of the folder. Arguments: - `subfolder_id` (integer, required): Subfolder id (from list_subfolders) #### `move_documents_to_subfolder` – Move documents to a subfolder (destructive) Move documents of a folder into one of its subfolders, or back to the top of the folder (subfolder_id omitted or 0). Arguments: - `folder_id` (integer, required): Folder id (from list_folders) - `document_ids` (array, required): Document ids (from list_documents / search) - `subfolder_id` (integer): Subfolder id; 0 = the top of the folder #### `list_room_folders` – List data room folders (read-only) Folders of a data room with their document counts, and the room's documents in index order (room_link_id, index number, room folder, the folder they come from). Arguments: - `data_room_id` (integer, required): Data room id (from list_data_rooms) #### `set_room_folders_enabled` – Turn data room folders on or off (write) Turn folders on or off for a data room (off by default). Turning them off deletes the room's folders; their documents stay in the room at the top level. Arguments: - `data_room_id` (integer, required): Data room id (from list_data_rooms) - `enabled` (boolean, required) #### `create_room_folder` – Create a data room folder (write) Create a folder in a data room to group its documents (one level, no folders inside folders). Data room folders are off by default – turn them on for the room first (set_room_folders_enabled, or "Use folders to group documents" in the data room settings). Arguments: - `data_room_id` (integer, required): Data room id (from list_data_rooms) - `name` (string, required) #### `rename_room_folder` – Rename a data room folder (destructive) Rename a folder of a data room. Arguments: - `data_room_id` (integer, required): Data room id (from list_data_rooms) - `room_folder_id` (integer, required): Data room folder id (from list_room_folders) - `name` (string, required) #### `delete_room_folder` – Delete a data room folder (destructive) Delete a folder of a data room. Its documents stay in the room (at the top level) – nothing is removed from the room or deleted. Arguments: - `data_room_id` (integer, required): Data room id (from list_data_rooms) - `room_folder_id` (integer, required): Data room folder id (from list_room_folders) #### `move_room_documents` – Move data room documents (destructive) Move documents of a data room into one of its folders, or back to the room's top level (room_folder_id omitted or 0). Pass room_link_ids (from list_room_folders) or document_ids. Arguments: - `data_room_id` (integer, required): Data room id (from list_data_rooms) - `room_link_ids` (array): Room document ids (room_link_id) - `document_ids` (array): Document ids (from list_documents / search) - `room_folder_id` (integer): Data room folder id; 0 = the top level #### `add_room_documents` – Add documents to a data room (write) Add documents from the user's folders to a data room, optionally straight into one of its folders (room_folder_id). The files stay in their folders. When the room sends e-mail notifications, its guests get one e-mail about the new documents. Arguments: - `data_room_id` (integer, required): Data room id (from list_data_rooms) - `document_ids` (array, required): Document ids (from list_documents / search) - `room_folder_id` (integer): Data room folder id (from list_room_folders) ## Recipes Also at https://www.helprange.com/developers/recipes. ### Send a deck to N investors and see who read it Uploads your pitch deck once, creates one tracked link per investor (each viewer verifies their e-mail, download blocked), and tells you who opened it and for how long. HelpRange does not e-mail the links through the API – you send each investor their own link. Prompt for a coding agent: "Using the HelpRange REST API (docs: https://www.helprange.com/developers/llms-full.txt, key in HELPRANGE_API_KEY), write a script that uploads ./deck.pdf, creates one share link per row of investors.csv (columns name,email) with label "<name>", require_email true and allow_download false, and writes name,email,url to links.csv. Then add a command that lists who opened the deck (GET /api/v2/views filtered by document_id) with seconds read." Prompt for an MCP chat (Claude, Cursor …): "In HelpRange: create a share link for my document "deck.pdf" for each of these investors – Anna (Fund A), Ben (Fund B) – labelled with their name, e-mail required, no download. Later: who opened deck.pdf this week and how long did they read?" ```python import csv, os, requests HR = "https://www.helprange.com/api/v2" H = {"Authorization": f"Bearer {os.environ['HELPRANGE_API_KEY']}"} # 1. upload the deck once with open("deck.pdf", "rb") as f: deck = requests.post(f"{HR}/files", headers=H, files={"file": f}, timeout=120).json() # 2. one tracked link per investor (label = who it is for; unique per document) with open("investors.csv") as src, open("links.csv", "w", newline="") as dst: out = csv.writer(dst) out.writerow(["name", "email", "url"]) for row in csv.DictReader(src): r = requests.post(f"{HR}/files/{deck['id']}/links", headers=H, timeout=30, json={"label": row["name"], "require_email": True, "allow_download": False}) r.raise_for_status() out.writerow([row["name"], row["email"], r.json()["url"]]) # send each investor their url # 3. who read it (or get it pushed: the document.opened webhook) for v in requests.get(f"{HR}/views", headers=H, timeout=30).json(): if v["document_id"] == deck["id"]: print(v["link_label"], v["viewer"], v["seconds_read"], "s", v["opened_at"]) stats = requests.get(f"{HR}/documents/{deck['id']}", headers=H, timeout=30).json() # time per page ``` ### Build a data room from files and invite guests with an NDA Fills a data room from a local folder (one room folder per subfolder) and invites the guests. Create the room and switch on its NDA once in the HelpRange panel (or start from a data room template) – the API has no "create data room" or "set NDA" endpoint yet. Every invited guest then accepts the NDA before seeing a document. Prompt for a coding agent: "Using the HelpRange REST API (docs: https://www.helprange.com/developers/llms-full.txt, key in HELPRANGE_API_KEY), write a script that takes a data room id and a local folder: check the room has an NDA (GET /api/v2/data-rooms/{id}, nda_required), turn room folders on, create one room folder per subfolder, upload every file, add the uploaded documents to their room folder, then invite every e-mail in guests.txt. Test it with an hr_test_ key first." Prompt for an MCP chat (Claude, Cursor …): "In my HelpRange data room "Project Falcon": turn on folders, create the folders Legal, Financials and Commercial, and add the documents from my folder "Falcon DD" to the right folder by their names. Then list the room folders with their documents." ```python import os, pathlib, requests HR = "https://www.helprange.com/api/v2" H = {"Authorization": f"Bearer {os.environ['HELPRANGE_API_KEY']}"} ROOM, SRC = 10, pathlib.Path("falcon-dd") # the room you created in the panel, with its NDA switched on room = requests.get(f"{HR}/data-rooms/{ROOM}", headers=H, timeout=30).json() assert room["nda_required"], "Switch on the room's NDA in HelpRange first" requests.post(f"{HR}/data-rooms/{ROOM}", headers=H, json={"folders_enabled": True}, timeout=30).raise_for_status() for sub in sorted(p for p in SRC.iterdir() if p.is_dir()): folder = requests.post(f"{HR}/data-rooms/{ROOM}/folders", headers=H, json={"name": sub.name}, timeout=30).json() ids = [] for path in sorted(p for p in sub.iterdir() if p.is_file()): with path.open("rb") as f: ids.append(requests.post(f"{HR}/files", headers=H, files={"file": f}, timeout=300).json()["id"]) if ids: # up to 100 per request requests.post(f"{HR}/data-rooms/{ROOM}/documents", headers=H, timeout=60, json={"document_ids": ids, "folder_id": folder["id"]}).raise_for_status() for email in pathlib.Path("guests.txt").read_text().split(): r = requests.post(f"{HR}/data-rooms/{ROOM}/guests", headers=H, json={"email": email}, timeout=30) print(email, r.status_code, r.json().get("already_a_guest")) # 201 invited, 200 already a guest ``` ### Post to Slack when someone opens a document A small webhook receiver: verifies HelpRange's signature, ignores duplicates and posts "ACME - Anna (anna@acme.com) opened proposal.pdf" to a Slack incoming webhook – for your own wording, filters or channel routing. No code needed for the plain version: paste a Slack incoming webhook URL in Settings → Integrations and HelpRange posts there itself. Prompt for a coding agent: "Write a minimal Flask app with POST /helprange-webhook that verifies the HelpRange-Signature header (HMAC-SHA256 of "<t>.<raw body>" with HELPRANGE_WEBHOOK_SECRET, 5-minute tolerance, docs: https://www.helprange.com/developers/webhooks), skips event ids it has already seen, and for document.opened events posts "<link> (<viewer>) opened <document>" to the Slack incoming webhook in SLACK_WEBHOOK_URL. Answer 200 quickly." ```python import hashlib, hmac, json, os, time import requests from flask import Flask, request app = Flask(__name__) SECRET = os.environ["HELPRANGE_WEBHOOK_SECRET"] # whsec_… (Settings → Integrations) SLACK = os.environ["SLACK_WEBHOOK_URL"] seen = set() # use your database in production def verified(raw: bytes, header: str) -> bool: parts = [p.split("=", 1) for p in (header or "").split(",") if "=" in p] t = next((v for k, v in parts if k == "t"), None) if t is None or not t.isdigit() or abs(time.time() - int(t)) > 300: return False expected = hmac.new(SECRET.encode(), t.encode() + b"." + raw, hashlib.sha256).hexdigest() return any(k == "v1" and hmac.compare_digest(v, expected) for k, v in parts) @app.post("/helprange-webhook") def helprange_webhook(): raw = request.get_data() if not verified(raw, request.headers.get("HelpRange-Signature")): return "bad signature", 400 event = json.loads(raw) if event["id"] in seen: # at-least-once delivery: the same id can arrive twice return "", 200 seen.add(event["id"]) if event["event"] == "document.opened": d = event["data"] requests.post(SLACK, json={"text": f"{d['link']} ({d['viewer']}) opened {d['document']}"}, timeout=5) return "", 200 ``` ## AGENTS.md section Also at https://www.helprange.com/developers/agents.md: ## Using HelpRange in this project HelpRange (secure document sharing, tracked links, virtual data rooms) is called through its REST API. - Base URL: `https://www.helprange.com/api/v2` (JSON in, JSON out; uploads also as multipart/form-data). - Auth: `Authorization: Bearer $HELPRANGE_API_KEY` – a team API key from HelpRange Settings → API keys. Never commit the key or send it from browser code; read it from the environment. - Test mode: keys starting with `hr_test_` work on a sandbox with sample data – no e-mail is sent, only addresses at example.com / example.org / *.test are accepted, nothing touches live data. Use a test key in development and CI; responses carry `"livemode": false`. - Scopes: each key has scopes (`documents:read`, `documents:write`, `links:read`, `links:write`, `rooms:read`, `rooms:write`, `analytics:read`, `team:read`). 403 `insufficient_scope` names the missing one in `required_scope`. - Full reference (Markdown, for LLMs): https://www.helprange.com/developers/llms-full.txt · OpenAPI 3.1: https://www.helprange.com/developers/openapi.json · Human docs: https://www.helprange.com/developers/api Key endpoints: - `GET /api/v2/me` – Who is connected - `GET /api/v2/folders` – List folders - `GET /api/v2/documents` – List documents - `POST /api/v2/files` – Upload a file - `POST /api/v2/files/{file_id}/links` – Create a share link - `GET /api/v2/documents/{file_id}/links` – List share links of a document - `GET /api/v2/documents/{file_id}` – Document statistics - `GET /api/v2/views` – Recent document views - `GET /api/v2/data-rooms` – List data rooms - `GET /api/v2/data-rooms/{data_room_id}` – Data room details - `POST /api/v2/data-rooms/{data_room_id}/documents` – Add documents to a data room - `POST /api/v2/data-rooms/{data_room_id}/guests` – Invite a guest - `POST /api/v2/signature-requests` – Send a document for e-signature - `POST /api/v2/offers` – Create an offer - `GET /api/v2/activity` – Team activity Errors: JSON `{"error": "…"}`, some with `code`. 400 invalid input (the message names the field), 401 bad / revoked / expired key, 402 plan without API access, 403 missing scope, 404 not found or not yours, 409 / 423 frozen data room, 413 file too large, 429 rate limit – wait `Retry-After` seconds (also `retry_after` in the body). Retries / idempotency: there is no Idempotency-Key header. GETs are safe to retry. Give every share link a unique `label` (per document): retrying a create with the same label answers 400 instead of making a duplicate – then list the document's links (`GET /api/v2/documents/{file_id}/links`). Inviting someone who is already a guest answers 200 with `already_a_guest: true`. Back off on 429 and 5xx. Rules worth knowing: `expires_on` must be today or later (`YYYY-MM-DD`); `views_limit` 0 or null = no limit; booleans accept true/false, 1/0 or "true"/"false"; a passcode of only spaces = no passcode. The API never e-mails share links – send the returned `url` yourself. Usage limits per team: 3,600 requests an hour (120 a minute), 300 writes an hour, 2,000 new links and 1,000 guests a day. Webhooks (set up in Settings → Integrations, not through the API): verify `HelpRange-Signature: t=…,v1=…` = HMAC-SHA256(secret, "<t>.<raw body>"), reject timestamps older than 5 minutes, deduplicate on the event `id`, answer 2xx fast. Docs: https://www.helprange.com/developers/webhooks