---
title: HelpRange API Recipes: Investor Deck Tracking, Data Rooms with NDA, Slack Alerts | HelpRange
url: https://www.helprange.com/developers/recipes
---

Developers · Recipes

# HelpRange API recipes

End-to-end examples. Each has what it does, a prompt to paste into Claude Code, Cursor or another AI coding agent, and the code. Run them with an `hr_test_` key first – the sandbox never sends e-mail.

 [### 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 …](#deck-to-investors)[### 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 …](#data-room-with-nda)[### 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 …](#webhook-to-slack)

## 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 your AI 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.
```
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.

### Or ask in a chat connected to the MCP server

```
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?
```
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?

MCP tools used: `upload_file`, `create_link`, `list_recent_views`, `get_document_stats`. [Connect your AI tool](https://www.helprange.com/developers/ai).

### The code Example

```
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
```
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

Endpoints: `/api/v2/files`, `/api/v2/files/{file_id}/links`, `/api/v2/views`, `/api/v2/documents/{file_id}` – details in the [API reference](https://www.helprange.com/developers/api).

## 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 your AI 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.
```
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.

### Or ask in a chat connected to the MCP server

```
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.
```
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.

MCP tools used: `get_data_room`, `set_room_folders_enabled`, `create_room_folder`, `add_room_documents`, `list_room_folders`. [Connect your AI tool](https://www.helprange.com/developers/ai).

### The code Example

```
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
```
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

Endpoints: `/api/v2/data-rooms/{data_room_id}`, `/api/v2/files`, `/api/v2/data-rooms/{data_room_id}/folders`, `/api/v2/data-rooms/{data_room_id}/documents`, `/api/v2/data-rooms/{data_room_id}/guests` – details in the [API reference](https://www.helprange.com/developers/api).

## 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 your AI 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.
```
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.

### The code Example

```
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
```
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

Event format, headers and retries: [webhooks](https://www.helprange.com/developers/webhooks).
