Developer guide
Integration examples
Build a CRM export, capture leads from another application and explore a scoped AI assistant workflow.
On this page
Choose a workflow
These examples use the documented public interfaces. They run on your server and read credentials from environment variables. Start with the quickstart so the workroom, key purpose and scope are already clear.
| Business outcome | Example | Prerequisites |
|---|---|---|
| Feed a reporting process with contacts | Paginated CRM export | Data Access key, CRM permission and a selected CRM space |
| Capture a submitted lead | Retry-safe incoming event | Integration key with CRM permission; persistent submission ID |
| Summarize CRM records with an assistant | MCP discovery workflow | Workroomly account and a read-only OAuth grant |
| Build an analytics report | Power BI setup | Data Access key and an authorized dataset |
Export CRM contacts
The business task is to obtain a bounded contact export for another server-side reporting process. This Python 3 example queries one CRM space in stable ID order, handles pagination, retries transient failures and writes JSON Lines. JSON avoids spreadsheet formula interpretation. Treat the resulting file as customer data and store it with appropriate access controls.
Set WORKROOMLY_API_KEY and WORKROOMLY_CRM_SPACE_KEY, then save and run the script. It creates contacts.jsonl only after every page succeeds; an existing output file is left intact if the export fails. Do not run multiple instances against the same output path.
import json
import os
import random
import tempfile
import time
import urllib.error
import urllib.request
from pathlib import Path
key = os.environ["WORKROOMLY_API_KEY"]
space = os.environ["WORKROOMLY_CRM_SPACE_KEY"]
output = Path("contacts.jsonl")
def query(offset):
body = json.dumps({
"resource": "crm.contact",
"scope": {"crm_space_key": space},
"fields": ["id", "name", "email", "updated_at"],
"sort": ["id"], "limit": 100, "offset": offset,
}).encode()
for attempt in range(4):
request = urllib.request.Request(
"https://api.workroomly.com/api/query", data=body,
headers={"Authorization": "Bearer " + key,
"Content-Type": "application/json"}, method="POST")
try:
with urllib.request.urlopen(request, timeout=30) as response:
return json.load(response)
except urllib.error.HTTPError as error:
request_id = error.headers.get("X-Request-ID", "unknown")
error.close()
if error.code not in (429, 500, 502, 503, 504) or attempt == 3:
raise RuntimeError(f"HTTP {error.code}; request {request_id}") from None
except (urllib.error.URLError, TimeoutError):
if attempt == 3:
raise
time.sleep(min(2 ** attempt, 8) + random.random())
raise RuntimeError("Retry limit reached")
pending = None
try:
with tempfile.NamedTemporaryFile(
mode="w", encoding="utf-8", dir=output.parent,
prefix="contacts-", suffix=".pending", delete=False
) as destination:
pending = Path(destination.name)
offset = 0
while True:
page = query(offset)
rows = page["data"]
for row in rows:
destination.write(json.dumps(row, ensure_ascii=False) + "\n")
offset += len(rows)
if not rows or offset >= page["meta"]["total"]:
break
os.replace(pending, output)
print(f"Exported {offset} contacts to {output}")
finally:
if pending is not None:
pending.unlink(missing_ok=True)Expected outcome: one JSON object per contact, containing only the selected columns. A zero-row export is valid. Confirm the key’s workroom and CRM scope before concluding that no contacts exist. Offset pagination can shift while records change; this is not a point-in-time backup. For larger analytical reads, use OData nextLink continuation and reconcile on subsequent refreshes.
Submit a lead with retries
Your website should send a submission to your own backend. That backend validates the form, records the submission durably, and sends an incoming event to Workroomly. The public form never receives a Workroomly key.
Prepare event.json from the lead event example. Replace the scope key and persist a unique idempotency key tied to your stored submission. Set WORKROOMLY_INTEGRATION_KEY. This Python 3 sender retries the exact bytes after transient failures, and prints the event receipt for storage in your own submission record.
import json
import os
import random
import time
import urllib.error
import urllib.request
from pathlib import Path
body = Path("event.json").read_bytes()
payload = json.loads(body)
if not payload.get("idempotency_key"):
raise ValueError("Persist an idempotency key before sending")
for attempt in range(4):
request = urllib.request.Request(
"https://api.workroomly.com/api/events", data=body,
headers={
"Authorization": "Bearer " + os.environ["WORKROOMLY_INTEGRATION_KEY"],
"Content-Type": "application/json",
}, method="POST")
try:
with urllib.request.urlopen(request, timeout=30) as response:
receipt = json.load(response)
print(json.dumps(receipt)) # Persist event_id with your submission.
break
except urllib.error.HTTPError as error:
status = error.code
request_id = error.headers.get("X-Request-ID", "unknown")
error.close()
if status not in (429, 500, 502, 503, 504) or attempt == 3:
raise RuntimeError(f"HTTP {status}; request {request_id}") from None
except (urllib.error.URLError, TimeoutError):
if attempt == 3:
raise
time.sleep(min(2 ** attempt, 8) + random.random())Expected outcome: 202 for first acceptance or 200 for a duplicate. Neither is proof that a lead has finished processing. Inspect Event Activity, then verify the CRM record. If delivery remains uncertain after the retry limit, keep the submission pending and retry later with its original key and payload. For 409, compare your persisted payload; do not replace the idempotency key merely to suppress the conflict. For 422, correct the input using Event Explorer before creating a new intended submission.
Summarize CRM context with an assistant
Connect your assistant to Workroomly and authorize a crm:read connection. Ask the assistant to list workrooms and spaces, then choose the intended business context. Ask it to retrieve the relevant contacts or deal and summarize only the records it can actually read.
A useful request is: “Find contacts matching Alex in my selected CRM space. Show me the matches before opening a record, and include the record ID in your summary.” The assistant should use discovery, crm_search, then crm_get_contact for your selection. It must not assume a similarly named record is the right person.
No write scope is necessary for this workflow. If you later want to create a prospect or update a deal, review the exact change, permissions and notification behavior first. Follow the MCP guide to configure permissions and handle writes.
Keep the request ID, endpoint and time of the failure. Remove credentials and private records before contacting Workroomly.