# MedReps Scraper \[$3.50/1K💰] | Medical Sales Jobs | OTE (`ahmed_jasarevic/medreps-scraper`) Actor

Scrape MedReps medical sales jobs: base salary and OTE ranges, territory, therapeutic area and contact emails. Filter by keyword, segment, state or remote. Export to CSV.

- **URL**: https://apify.com/ahmed_jasarevic/medreps-scraper.md
- **Developed by:** [Ahmed Jasarevic](https://apify.com/ahmed_jasarevic) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## MedReps Scraper – Medical Sales Jobs, Base Salary & OTE Data

Extract **medical device, pharma, biotech and capital-equipment sales jobs** from MedReps: title, hiring company, segment, therapeutic area, territory, base salary and OTE ranges, travel percentage, experience, education, posting date, contact email and apply URL. **$3.50 per 1,000 jobs.** Filter by keyword, segment, state or remote.

### What Can You Do With MedReps Data?

- **Compensation benchmarking** – compare base salary and OTE ranges by segment, therapeutic area and territory
- **Recruiter candidate sourcing** – map open territories and the companies hiring in them
- **Territory coverage analysis** – see which states and metros have the most open medical-sales roles
- **Sales-comp modelling** – build a base/OTE distribution for a device or pharma niche
- **Job-board aggregation** – feed a niche medical-sales board with fresh postings
- **Market entry research** – gauge which manufacturers are expanding commercial teams, and where

### How It Works

![How the MedReps Scraper works: from a job index query to structured compensation rows.](https://api.apify.com/v2/key-value-stores/oeuA0kLOyhbRM4fzt/records/how-it-works-medreps-scraper.webp)

The Actor collects publicly available MedReps job postings and normalizes them into one schema. Provide a keyword, segment, state or remote filter; it reads the published job search and posting pages, then writes one row per active job.

- A full scan covers the active 30-day index, about 3,100 postings.
- Salary, OTE and territory fields are taken from each posting.
- `maxItems` caps the number of saved jobs.

### Scrape Medical Sales Jobs And Compensation Data

MedReps does not publish an open read API for research. Its employer-side tools are for companies posting jobs, not for reading the market.

This Actor collects the public job postings shown on MedReps and returns them as rows, with no employer account or job-feed contract.

### Base Salary And OTE Ranges In $K

Compensation is the reason most people want this data. `baseSalaryMinK` and `baseSalaryMaxK` hold the base range in thousands of USD; `oteMinK` and `oteMaxK` hold the on-target-earnings range, and `compensationType` records the structure ("Base + Commission", and similar).

These are the ranges the employer published, not a computed average. Filtering by `productCategories` and comparing OTE across segments is the fastest way to price a role or sanity-check an offer.

### Territory, Travel, Experience And Therapeutic Area

`city`, `state`, `states[]`, `country` and `locations[]` describe the territory; `isRemote` and `isFieldBased` flag the working pattern; `travelPercentage` uses MedReps' own buckets (0–25%, 25–50%, 50–75%, 75–100%).

`therapeuticArea` and `therapeuticAreas[]` are inferred from the posting text and title — cardiology, oncology, orthopedics, neurology, GI, urology and similar. `experienceRequired`, `experienceYears` and `educationRequired` are parsed from the description, so they are best-effort: the raw `description` and `descriptionText` are always included so you can verify.

### Input – Configure A Job Search

| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| `searchQuery` | string | No | — | Keyword passed to MedReps search (`cardiac`, `oncology`, `territory manager`) |
| `productCategories` | array | No | — | Segment filter; empty = all segments |
| `states` | array | No | — | Two-letter state codes; empty = all territories |
| `remoteOnly` | boolean | No | `false` | Keep only postings flagged remote |
| `maxItems` | integer | No | `100` | Max jobs saved |
| `proxy` | object | No | — | Standard Apify proxy configuration |

### Output – Job Posting Fields

| Field | Type | Description |
|---|---|---|
| `jobId`, `jobUrl` | string | MedReps posting id and public job-detail URL |
| `title` | string | Job title |
| `company`, `companyId`, `companyWebsite`, `companyLogoUrl`, `isStaffingFirm` | string / boolean | Hiring company or recruiter |
| `segment`, `productCategory` | string | Device / pharma / biotech / capital equipment / DME / services / IT |
| `therapeuticArea`, `therapeuticAreas` | string / array | Inferred therapeutic area(s) |
| `city`, `state`, `states`, `country`, `locations` | string / array | Territory and coordinates when available |
| `isRemote`, `isFieldBased` | boolean | Working pattern flags |
| `baseSalaryMinK`, `baseSalaryMaxK` | number | Base range in $K |
| `oteMinK`, `oteMaxK` | number | OTE range in $K |
| `compensationType` | string | e.g. Base + Commission |
| `travelPercentage` | string | Travel bucket |
| `experienceRequired`, `experienceYears` | string / number | Parsed from the posting |
| `educationRequired` | string | Parsed from the posting |
| `description`, `descriptionText` | string | Full posting, HTML and plain text |
| `postedAt`, `createdAt`, `archivedAt` | string | ISO timestamps |
| `contactEmail`, `applicationMethod`, `applicationUrl`, `applyUrl` | string | Contact and best apply link |
| `isFeatured`, `isExclusive`, `isConfidential`, `jobCode` | boolean / string | Posting flags |

### Example Input – Cardiology Roles In Texas

```json
{
  "searchQuery": "cardiology",
  "productCategories": ["Medical Device"],
  "states": ["TX"],
  "maxItems": 50,
  "remoteOnly": false
}
```

### Example Output – One Job Posting

```json
{
  "jobId": "a1b2c3d4",
  "title": "Territory Manager",
  "jobUrl": "https://www.medreps.com/job-detail/territory-manager-12345/",
  "company": "Confidential",
  "segment": "Medical Device",
  "productCategory": "medical-device",
  "therapeuticArea": "Cardiology",
  "city": "Dallas",
  "state": "TX",
  "isRemote": false,
  "isFieldBased": true,
  "baseSalaryMinK": 95,
  "baseSalaryMaxK": 120,
  "oteMinK": 170,
  "oteMaxK": 190,
  "compensationType": "Base + Commission",
  "travelPercentage": "50-75%",
  "experienceRequired": "3+ years",
  "postedAt": "2026-10-20T00:00:00.000Z",
  "applyUrl": "https://www.medreps.com/job-detail/territory-manager-12345/"
}
```

### Export MedReps Data To JSON, CSV Or Excel And Schedule Runs

Results are written to the default dataset: JSON, CSV, Excel or XML from the run page, or through the Apify API. A full index scan can take a few minutes; a filtered `maxItems` run is much faster.

Postings turn over on a 30-day window, so a **daily or weekly schedule** keeps a board or a comp dataset fresh. At $3.50 per 1,000 jobs, the entire active index (about 3,100 postings) costs roughly $11.

### Related Actors

- [Google Search Scraper](https://apify.com/apify/google-search-scraper) – research a hiring company, its products and its commercial footprint
- [Website Content Crawler](https://apify.com/apify/website-content-crawler) – pull company career pages and product pages into clean text
- [Google Maps Scraper](https://apify.com/apify/google-maps-scraper) – map territory cities and hospital clusters
- [Docplanner Scraper](https://apify.com/ahmed_jasarevic/docplanner-scraper) – healthcare provider directory data for territory and account mapping

### FAQ

#### Why use this actor instead of the MedReps website?

Because the site is built for one job at a time. This actor returns the whole active index — or a filtered slice — as a table with base salary, OTE, territory and contact email, so you can sort by comp instead of clicking through postings.

#### Is there a MedReps API?

Not a public read API. MedReps offers employer tools for posting jobs, but not a way to read the market. This Actor collects the public job postings shown on MedReps.

#### Why do some postings show no salary?

Because the employer did not publish a range. `baseSalaryMinK`, `baseSalaryMaxK`, `oteMinK` and `oteMaxK` are `null` in that case — the actor does not estimate compensation. Filter those rows out before computing averages.

#### How current is the data?

MedReps shows a rolling 30-day window of active postings, so every row is from the last 30 days. The index held roughly 3,100 jobs when last measured.

#### Are `therapeuticArea` and `experienceRequired` official fields?

No. They are inferred from the title and description text, because MedReps does not expose them as structured fields. The raw `description` and `descriptionText` are always included so you can verify or re-parse them.

#### What are alternatives for medical sales job data?

General job APIs (Adzuna, Jooble) cover healthcare sales loosely; LinkedIn and Indeed carry some of the same roles. MedReps is the specialist board, so it has the densest coverage of device and pharma field sales — and the only one where OTE is commonly published.

#### Does the `states` filter reduce the run size?

No. The state, segment and remote filters are applied after collection, so a narrow filter still scans the index. Use `searchQuery` to narrow results at the source and `maxItems` to bound cost.

### For AI Agents & LLM Apps

Returns one row per active MedReps job posting with compensation ranges, territory, segment and contact details.

Minimal working input:

```json
{ "searchQuery": "territory manager", "maxItems": 20 }
```

Variant — remote roles only, no keyword:

```json
{ "remoteOnly": true, "maxItems": 50 }
```

Output fields: `jobId, title, jobUrl, company, companyId, companyWebsite, isStaffingFirm, segment, productCategory, therapeuticArea, therapeuticAreas, city, state, states, country, locations, isRemote, isFieldBased, baseSalaryMinK, baseSalaryMaxK, oteMinK, oteMaxK, compensationType, travelPercentage, experienceRequired, experienceYears, educationRequired, description, descriptionText, postedAt, createdAt, archivedAt, contactEmail, applicationMethod, applicationUrl, applyUrl, isFeatured, isExclusive, isConfidential, jobCode`.

Behaviors an agent should know:

- `maxItems` defaults to **100**; the full index is about 3,100 postings over ~155 pages, so a large cap means a long run.
- Only `searchQuery` narrows the source search. `productCategories`, `states` and `remoteOnly` are applied to collected rows, so they do not reduce run size.
- Salary and OTE fields are `null` when the employer published no range — never impute them.
- `therapeuticArea`, `experienceRequired` and `educationRequired` are text inferences, not source fields; use `descriptionText` to verify.
- Every row is inside MedReps' rolling 30-day window; there is no archive or historical parameter.
- `contactEmail` may be absent, and `company` can be `Confidential` by design.

Billing: **$0.0035 per saved job** ($3.50 per 1,000), plus a small actor-start event.

### Legal & Compliance Disclaimer

This actor is an independent tool. It is not affiliated with, endorsed by, or sponsored by MedReps or Healthcare Staffing Technologies.

It collects publicly available job postings — no login, no employer account, no paywall bypass, no CAPTCHA solving. Job descriptions and salary ranges are published by employers for public distribution.

Some records contain a **contact email** for the recruiter or hiring company. That is business contact data published for applications; it must not be used for unsolicited bulk outreach. In the US, commercial email is regulated by CAN-SPAM and telemarketing by the TCPA; in the EU and UK, GDPR and ePrivacy apply. Users are responsible for complying with MedReps' Terms of Service and with applicable employment-advertising and data-protection law. This section is a factual disclosure, not legal advice.

### SEO Keywords

medreps scraper, medreps api, medical sales jobs, medical device sales jobs, pharmaceutical sales jobs, medtech jobs data, medical sales salary data, ote data, territory manager jobs, pharma sales recruiter, medical sales job board, healthcare sales jobs, base salary and ote, medical sales compensation, device sales territory data, job board scraper, medreps alternative, medical sales hiring data

# Actor input Schema

## `searchQuery` (type: `string`):

Optional keyword passed to the MedReps job search (e.g. "cardiac", "oncology", "territory manager"). Leave empty to pull every active medical device / pharma sales job in the index.

## `productCategories` (type: `array`):

Optional filter on MedReps product category. Leave empty for all segments.

## `states` (type: `array`):

Optional two-letter US state codes to keep (e.g. TX, CA, FL). Leave empty for all territories.

## `remoteOnly` (type: `boolean`):

If enabled, only job postings flagged as remote are returned.

## `maxItems` (type: `integer`):

Maximum number of job postings to save to the dataset. Each API page returns 20 jobs, so a full index scan is about 155 requests.

## `proxy` (type: `object`):

Select proxies to be used by your crawler.

## Actor input object example

```json
{
  "searchQuery": "",
  "remoteOnly": false,
  "maxItems": 100,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQuery": "",
    "proxy": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ahmed_jasarevic/medreps-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchQuery": "",
    "proxy": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("ahmed_jasarevic/medreps-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQuery": "",
  "proxy": {
    "useApifyProxy": false
  }
}' |
apify call ahmed_jasarevic/medreps-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ahmed_jasarevic/medreps-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/eNRY8F4WruvWer9EA/builds/WVbv8JfzstKp4qxDS/openapi.json
