Healthcare portal extraction
Log into a sandbox healthcare portal and extract patient medication records from the dashboard.
- A Browserless API token from your account dashboard
Steps
This example uses a Browserless sandbox site that simulates a healthcare portal with a login screen. You can safely test data extraction without accessing real patient data.
The workflow navigates to the patient portal, logs in with demo credentials, waits for the dashboard to render, then extracts medication records from the medications table.
- AI Agent
- REST API
- Frameworks
- BQL
Use the Browserless MCP server to extract data from the healthcare portal from any MCP-compatible AI agent (Claude Desktop, Cursor, Windsurf, ChatGPT, etc.).
1. Connect the MCP server
Send this prompt to your AI agent to install the Browserless MCP server:
Go to https://github.com/browserless/browserless-mcp/blob/main/install.md
and follow the instructions to install the Browserless MCP server
for my client.
2. Extract portal data
Use browserless_agent to log in and extract medication data.
Use the browserless_agent tool to log into
https://scraping-sandbox.netlify.app/clarity-health/patient-portal
with patient@example.com / health2025 and get the most recent invoice.
Send the BQL mutation over HTTP to log in and extract medication data. This is a sandbox site, so no stealth mode is needed.
- cURL
- JavaScript
- Python
- Java
- C#
1. Send the request
curl -X POST \
"https://production-sfo.browserless.io/chromium/bql?token=YOUR_API_TOKEN_HERE" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation HealthcarePortal { goto(url: \"https://scraping-sandbox.netlify.app/clarity-health/patient-portal\", waitUntil: networkIdle) { status } waitForLogin: waitForSelector(selector: \"#patient-email\", timeout: 10000) { time } typeEmail: type(selector: \"#patient-email\", text: \"patient@example.com\") { time } typePassword: type(selector: \"#patient-password\", text: \"health2025\") { time } submitLogin: click(selector: \"#patient-login-submit\") { time } waitForDashboard: waitForSelector(selector: \"#medicationlist\", timeout: 10000) { time } medications: mapSelector(selector: \"#medicationlist table tbody tr\") { medication: mapSelector(selector: \"td:nth-child(1)\") { innerText } dosage: mapSelector(selector: \"td:nth-child(2)\") { innerText } frequency: mapSelector(selector: \"td:nth-child(3)\") { innerText } prescriber: mapSelector(selector: \"td:nth-child(4)\") { innerText } refills: mapSelector(selector: \"td:nth-child(5)\") { innerText } } }",
"variables": {}
}'
2. Check the output
{
"data": {
"goto": { "status": 200 },
"waitForLogin": { "time": 2 },
"typeEmail": { "time": 2328 },
"typePassword": { "time": 1567 },
"submitLogin": { "time": 152 },
"waitForDashboard": { "time": 780 },
"medications": [
{
"medication": [{ "innerText": "Atorvastatin" }],
"dosage": [{ "innerText": "10 mg — Oral" }],
"frequency": [{ "innerText": "Once daily at bedtime" }],
"prescriber": [{ "innerText": "Dr. Marcus Webb" }],
"refills": [{ "innerText": "4" }]
},
{
"medication": [{ "innerText": "Vitamin D3" }],
"dosage": [{ "innerText": "2000 IU — Oral" }],
"frequency": [{ "innerText": "Once daily" }],
"prescriber": [{ "innerText": "Dr. Sarah Chen" }],
"refills": [{ "innerText": "2" }]
}
]
}
}
1. Send the request
const query = `mutation HealthcarePortal {
goto(url: "https://scraping-sandbox.netlify.app/clarity-health/patient-portal", waitUntil: networkIdle) {
status
}
waitForLogin: waitForSelector(selector: "#patient-email", timeout: 10000) {
time
}
typeEmail: type(selector: "#patient-email", text: "patient@example.com") {
time
}
typePassword: type(selector: "#patient-password", text: "health2025") {
time
}
submitLogin: click(selector: "#patient-login-submit") {
time
}
waitForDashboard: waitForSelector(selector: "#medicationlist", timeout: 10000) {
time
}
medications: mapSelector(selector: "#medicationlist table tbody tr") {
medication: mapSelector(selector: "td:nth-child(1)") { innerText }
dosage: mapSelector(selector: "td:nth-child(2)") { innerText }
frequency: mapSelector(selector: "td:nth-child(3)") { innerText }
prescriber: mapSelector(selector: "td:nth-child(4)") { innerText }
refills: mapSelector(selector: "td:nth-child(5)") { innerText }
}
}`;
const response = await fetch(
'https://production-sfo.browserless.io/chromium/bql?token=YOUR_API_TOKEN_HERE',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables: {} }),
}
);
const { data } = await response.json();
const medications = data.medications.map((row) => ({
medication: row.medication?.[0]?.innerText ?? '',
dosage: row.dosage?.[0]?.innerText ?? '',
frequency: row.frequency?.[0]?.innerText ?? '',
prescriber: row.prescriber?.[0]?.innerText ?? '',
refills: row.refills?.[0]?.innerText ?? '',
}));
console.log(JSON.stringify(medications, null, 2));
2. Check the output
[
{
"medication": "Atorvastatin",
"dosage": "10 mg — Oral",
"frequency": "Once daily at bedtime",
"prescriber": "Dr. Marcus Webb",
"refills": "4"
},
{
"medication": "Vitamin D3",
"dosage": "2000 IU — Oral",
"frequency": "Once daily",
"prescriber": "Dr. Sarah Chen",
"refills": "2"
}
]
1. Install dependencies
pip install requests
2. Send the request
import requests
query = """
mutation HealthcarePortal {
goto(url: "https://scraping-sandbox.netlify.app/clarity-health/patient-portal", waitUntil: networkIdle) {
status
}
waitForLogin: waitForSelector(selector: "#patient-email", timeout: 10000) {
time
}
typeEmail: type(selector: "#patient-email", text: "patient@example.com") {
time
}
typePassword: type(selector: "#patient-password", text: "health2025") {
time
}
submitLogin: click(selector: "#patient-login-submit") {
time
}
waitForDashboard: waitForSelector(selector: "#medicationlist", timeout: 10000) {
time
}
medications: mapSelector(selector: "#medicationlist table tbody tr") {
medication: mapSelector(selector: "td:nth-child(1)") { innerText }
dosage: mapSelector(selector: "td:nth-child(2)") { innerText }
frequency: mapSelector(selector: "td:nth-child(3)") { innerText }
prescriber: mapSelector(selector: "td:nth-child(4)") { innerText }
refills: mapSelector(selector: "td:nth-child(5)") { innerText }
}
}
"""
response = requests.post(
'https://production-sfo.browserless.io/chromium/bql',
params={'token': 'YOUR_API_TOKEN_HERE'},
json={'query': query, 'variables': {}},
)
data = response.json()['data']
for med in data['medications']:
name = med['medication'][0]['innerText']
dosage = med['dosage'][0]['innerText']
frequency = med['frequency'][0]['innerText']
prescriber = med['prescriber'][0]['innerText']
refills = med['refills'][0]['innerText']
print(f'{name} | {dosage} | {frequency} | {prescriber} | {refills}')
3. Check the output
Atorvastatin | 10 mg — Oral | Once daily at bedtime | Dr. Marcus Webb | 4
Vitamin D3 | 2000 IU — Oral | Once daily | Dr. Sarah Chen | 2
Naproxen | 500 mg — Oral | Twice daily with food as needed | Dr. James Torres | 1
1. Send the request
import java.net.URI;
import java.net.http.*;
String token = "YOUR_API_TOKEN_HERE";
String endpoint = "https://production-sfo.browserless.io/chromium/bql?token=" + token;
String query = "mutation HealthcarePortal {"
+ " goto(url: \\\"https://scraping-sandbox.netlify.app/clarity-health/patient-portal\\\", waitUntil: networkIdle) { status }"
+ " waitForLogin: waitForSelector(selector: \\\"#patient-email\\\", timeout: 10000) { time }"
+ " typeEmail: type(selector: \\\"#patient-email\\\", text: \\\"patient@example.com\\\") { time }"
+ " typePassword: type(selector: \\\"#patient-password\\\", text: \\\"health2025\\\") { time }"
+ " submitLogin: click(selector: \\\"#patient-login-submit\\\") { time }"
+ " waitForDashboard: waitForSelector(selector: \\\"#medicationlist\\\", timeout: 10000) { time }"
+ " medications: mapSelector(selector: \\\"#medicationlist table tbody tr\\\") {"
+ " medication: mapSelector(selector: \\\"td:nth-child(1)\\\") { innerText }"
+ " dosage: mapSelector(selector: \\\"td:nth-child(2)\\\") { innerText }"
+ " frequency: mapSelector(selector: \\\"td:nth-child(3)\\\") { innerText }"
+ " prescriber: mapSelector(selector: \\\"td:nth-child(4)\\\") { innerText }"
+ " refills: mapSelector(selector: \\\"td:nth-child(5)\\\") { innerText }"
+ " }"
+ " }";
String payload = "{\"query\": \"" + query + "\", \"variables\": {}}";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
2. Check the output
{
"data": {
"goto": { "status": 200 },
"waitForDashboard": { "time": 780 },
"medications": [
{
"medication": [{ "innerText": "Atorvastatin" }],
"prescriber": [{ "innerText": "Dr. Marcus Webb" }]
}
]
}
}
1. Send the request
using System.Net.Http;
using System.Text;
using System.Text.Json;
string token = "YOUR_API_TOKEN_HERE";
string endpoint = $"https://production-sfo.browserless.io/chromium/bql?token={token}";
var payload = new
{
query = @"mutation HealthcarePortal {
goto(url: ""https://scraping-sandbox.netlify.app/clarity-health/patient-portal"", waitUntil: networkIdle) { status }
waitForLogin: waitForSelector(selector: ""#patient-email"", timeout: 10000) { time }
typeEmail: type(selector: ""#patient-email"", text: ""patient@example.com"") { time }
typePassword: type(selector: ""#patient-password"", text: ""health2025"") { time }
submitLogin: click(selector: ""#patient-login-submit"") { time }
waitForDashboard: waitForSelector(selector: ""#medicationlist"", timeout: 10000) { time }
medications: mapSelector(selector: ""#medicationlist table tbody tr"") {
medication: mapSelector(selector: ""td:nth-child(1)"") { innerText }
dosage: mapSelector(selector: ""td:nth-child(2)"") { innerText }
frequency: mapSelector(selector: ""td:nth-child(3)"") { innerText }
prescriber: mapSelector(selector: ""td:nth-child(4)"") { innerText }
refills: mapSelector(selector: ""td:nth-child(5)"") { innerText }
}
}",
variables = new { },
};
using (HttpClient httpClient = new HttpClient())
{
var content = new StringContent(
JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(endpoint, content);
string body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
}
2. Check the output
{
"data": {
"goto": { "status": 200 },
"waitForDashboard": { "time": 780 },
"medications": [
{
"medication": [{ "innerText": "Atorvastatin" }],
"prescriber": [{ "innerText": "Dr. Marcus Webb" }]
}
]
}
}
Connect a headless browser to the sandbox portal, log in, and extract medication records from the dashboard.
- Puppeteer
- Playwright
1. Install dependencies
npm install puppeteer-core
2. Connect and extract
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://production-sfo.browserless.io?token=YOUR_API_TOKEN_HERE',
});
try {
const page = await browser.newPage();
await page.goto('https://scraping-sandbox.netlify.app/clarity-health/patient-portal', {
waitUntil: 'networkidle2',
});
await page.waitForSelector('#patient-email');
await page.type('#patient-email', 'patient@example.com');
await page.type('#patient-password', 'health2025');
await page.click('#patient-login-submit');
await page.waitForSelector('#medicationlist');
const medications = await page.evaluate(() =>
Array.from(document.querySelectorAll('#medicationlist table tbody tr')).map((row) => {
const cells = row.querySelectorAll('td');
return {
medication: cells[0]?.innerText?.trim() ?? '',
dosage: cells[1]?.innerText?.trim() ?? '',
frequency: cells[2]?.innerText?.trim() ?? '',
prescriber: cells[3]?.innerText?.trim() ?? '',
refills: cells[4]?.innerText?.trim() ?? '',
};
})
);
console.log(JSON.stringify(medications, null, 2));
} finally {
await browser.close();
}
3. Check the output
Run with node healthcare-portal-extraction.mjs. Each object has medication, dosage, frequency, prescriber, and refills fields.
[
{
"medication": "Atorvastatin",
"dosage": "10 mg — Oral",
"frequency": "Once daily at bedtime",
"prescriber": "Dr. Marcus Webb",
"refills": "4"
}
]
1. Install dependencies
npm install playwright-core
2. Connect and extract
import { chromium } from 'playwright-core';
const browser = await chromium.connectOverCDP(
'wss://production-sfo.browserless.io?token=YOUR_API_TOKEN_HERE'
);
try {
const context = browser.contexts()[0];
const page = await context.newPage();
await page.goto('https://scraping-sandbox.netlify.app/clarity-health/patient-portal', {
waitUntil: 'networkidle',
});
await page.waitForSelector('#patient-email');
await page.fill('#patient-email', 'patient@example.com');
await page.fill('#patient-password', 'health2025');
await page.click('#patient-login-submit');
await page.waitForSelector('#medicationlist');
const medications = await page.evaluate(() =>
Array.from(document.querySelectorAll('#medicationlist table tbody tr')).map((row) => {
const cells = row.querySelectorAll('td');
return {
medication: cells[0]?.innerText?.trim() ?? '',
dosage: cells[1]?.innerText?.trim() ?? '',
frequency: cells[2]?.innerText?.trim() ?? '',
prescriber: cells[3]?.innerText?.trim() ?? '',
refills: cells[4]?.innerText?.trim() ?? '',
};
})
);
console.log(JSON.stringify(medications, null, 2));
} finally {
await browser.close();
}
3. Check the output
Run with node healthcare-portal-extraction.mjs. Each object has medication, dosage, frequency, prescriber, and refills fields.
[
{
"medication": "Atorvastatin",
"dosage": "10 mg — Oral",
"frequency": "Once daily at bedtime",
"prescriber": "Dr. Marcus Webb",
"refills": "4"
}
]
1. Write the mutation
Navigate to the sandbox healthcare portal, log in with demo credentials, and extract medication records. This is a sandbox site, so no stealth mode is needed.
mutation HealthcarePortal {
goto(url: "https://scraping-sandbox.netlify.app/clarity-health/patient-portal", waitUntil: networkIdle) {
status
}
waitForLogin: waitForSelector(selector: "#patient-email", timeout: 10000) {
time
}
typeEmail: type(selector: "#patient-email", text: "patient@example.com") {
time
}
typePassword: type(selector: "#patient-password", text: "health2025") {
time
}
submitLogin: click(selector: "#patient-login-submit") {
time
}
waitForDashboard: waitForSelector(selector: "#medicationlist", timeout: 10000) {
time
}
medications: mapSelector(selector: "#medicationlist table tbody tr") {
medication: mapSelector(selector: "td:nth-child(1)") { innerText }
dosage: mapSelector(selector: "td:nth-child(2)") { innerText }
frequency: mapSelector(selector: "td:nth-child(3)") { innerText }
prescriber: mapSelector(selector: "td:nth-child(4)") { innerText }
refills: mapSelector(selector: "td:nth-child(5)") { innerText }
}
}
2. Run it
Paste into the BQL IDE and click Run.
3. Check the output
{
"data": {
"goto": { "status": 200 },
"waitForLogin": { "time": 2 },
"typeEmail": { "time": 2328 },
"typePassword": { "time": 1567 },
"submitLogin": { "time": 152 },
"waitForDashboard": { "time": 780 },
"medications": [
{
"medication": [{ "innerText": "Atorvastatin" }],
"dosage": [{ "innerText": "10 mg — Oral" }],
"frequency": [{ "innerText": "Once daily at bedtime" }],
"prescriber": [{ "innerText": "Dr. Marcus Webb" }],
"refills": [{ "innerText": "4" }]
},
{
"medication": [{ "innerText": "Vitamin D3" }],
"dosage": [{ "innerText": "2000 IU — Oral" }],
"frequency": [{ "innerText": "Once daily" }],
"prescriber": [{ "innerText": "Dr. Sarah Chen" }],
"refills": [{ "innerText": "2" }]
}
]
}
}
Next steps
- Scrape Structured Data -- extract data from other sites using the
/scrapeendpoint - Fill and Submit a Form -- automate form interactions on portals
- Authenticated Sessions -- persist login state for portals that require authentication