# ApartmentJobs Scraper — Apartment Industry Job Listings (`crabwalker/apartmentjobs-scraper`) Actor

Extract apartment industry job listings from ApartmentJobs.com: titles, employers, locations, salary ranges, experience levels, rent-discount perks, full descriptions, and apply links.

- **URL**: https://apify.com/crabwalker/apartmentjobs-scraper.md
- **Developed by:** [Crab Walker](https://apify.com/crabwalker) (community)
- **Categories:** Jobs, Real estate
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 job listings

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## ApartmentJobs Scraper

Scrape apartment industry job listings from [ApartmentJobs.com](https://apartmentjobs.com), the National Apartment Association's job board — roughly 16,700 live openings for leasing consultants, maintenance technicians, property managers, community managers, and groundskeepers.

Search by keyword and location, filter by category, experience, salary band, or the employee rent-discount perk, or point the Actor straight at any job, company, city, state, or category page. No login or cookies are used.

By default the Actor visits each job page to return the full description, normalized location, job category, and remote flag alongside the search-card data.

### Input

The simplest input is:

```json
{
  "keywords": "maintenance technician",
  "location": "Atlanta, GA",
  "maxItems": 100
}
```

Narrow the search with a radius and the board's own facets:

```json
{
  "keywords": "leasing",
  "location": "Austin, TX",
  "radiusMiles": 50,
  "categories": ["leasing", "communityManager"],
  "experienceLevels": ["entryLevel", "from2To5Years"],
  "salaryRanges": ["from40kTo75k"],
  "rentDiscounts": ["upTo50"],
  "maxItems": 200,
  "includeDescription": true
}
```

Leave `keywords` and `location` empty to browse every listing on the board. Each selected category is searched separately, as are all `searchTerms` × `locations` combinations:

```json
{
  "searchTerms": ["porter", "groundskeeper"],
  "locations": ["Dallas, TX", "Houston, TX"],
  "maxItemsPerSearch": 25
}
```

`startUrls` accepts any `apartmentjobs.com` URL — a single job (`/job/108313/maintenance-technician/`), a company's openings (`/company/886/cottonwood-residential/`), a city (`/cities/jobs-in-austin/`), a state (`/states/jobs-in-texas/`), a category (`/categories/maintenance/`), remote roles (`/countries/remote-jobs/`), or a search URL you copied from the site. The aliases `keyword`, `position`, and `maxItemsPerSearch` are also supported.

### Output

Every row carries the same fields, whichever input produced it:

- Job ID, canonical URL, title, and apply URL
- Employer name, company profile URL, and logo
- Displayed location plus split city, state, and country
- Salary range, experience level, employment type, category, industry, and remote flag
- Advertised employee rent discount — a perk specific to this board
- Featured-listing flag, human-readable and ISO posting dates, and expiration date
- Plain-text and HTML descriptions
- Search provenance and scrape timestamp

Fields stay `null` when the board does not publish them for a listing. Salary, rent discount, and experience appear on search cards; descriptions, categories, and the remote flag require `includeDescription`.

#### Field notes

Four fields behave in ways worth knowing before you build on them:

- **`industry` is not an industry.** The board populates schema.org `industry` with the job category, so this field mirrors `category` and emits values like `"Community Manager"`. It describes the role, not the employer's line of business — never route it into a firmographic or ICP industry filter.
- **`salary` is not normalized.** It is the string the board displays, and the period varies per listing: `"$27.00 - $28.00 hourly"` and `"$75,000 - $85,000 yearly"` both occur. Parse the trailing unit before comparing figures.
- **`postedAt` is display text, `publishedAt` is a timestamp.** `postedAt` is what the page shows (`"Jul 30, 2026"`); `publishedAt` is RFC 3339 UTC and is the field to sort or filter on. Search cards carry a date only and are anchored to `00:00:00Z`; job pages carry a precise time.
- **`state` format depends on the source.** Search cards abbreviate (`"GA"`), job pages spell out (`"Georgia"`). Normalize if you join on it.

Featured listings repeat across result pages; `saveOnlyUniqueItems` (on by default) deduplicates them by job ID.

### Pricing

The Actor uses pay-per-event pricing. Platform usage is included.

| Event | Free | Bronze | Silver | Gold | Platinum | Diamond |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| Actor start (per run) | $0.001 | $0.001 | $0.001 | $0.001 | $0.001 | $0.001 |
| Job listing | $0.005 | $0.004 | $0.0035 | $0.003 | $0.002 | $0.0012 |
| Add-on: full job details | $0.002 | $0.0016 | $0.0013 | $0.0011 | $0.0009 | $0.0006 |

The add-on is charged only for jobs whose detail page was actually fetched, so `includeDescription: false` cuts the cost of a bulk export substantially. At the Free-tier rate, 1,000 jobs with full descriptions cost about $7.00; without descriptions, about $5.00. Failed requests and listings not written to the dataset are never charged.

### Responsible use

This Actor requests only publicly available, signed-out ApartmentJobs.com pages, and honours the paths the site's `robots.txt` disallows. Use reasonable limits and delays, respect applicable laws and the site's terms, and do not use scraped data for spam or for automated decisions about individuals.

ApartmentJobs.com answers datacenter address ranges with HTTP 403, so **US residential proxies are enabled by default and are effectively required**. Blocks are per exit IP rather than per fingerprint: sessions are sticky per search, a blocked address is retired and rotated immediately, and browser-level TLS/header impersonation is used without solving CAPTCHAs.

### Development and deployment

```bash
npm ci
npm test
npm run lint
```

Add `APIFY_API_TOKEN` as an encrypted GitHub Actions secret. Pull requests run tests and linting; pushes to `main` deploy the Actor.

# Actor input Schema

## `keywords` (type: `string`):

Job title, certification, or other search phrase, e.g. "maintenance technician". Leave empty to browse every listing.

## `location` (type: `string`):

City and state, e.g. "Atlanta, GA". Leave empty to search nationwide.

## `radiusMiles` (type: `string`):

Miles around the location to include. Applies only when a location is set.

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

Maximum number of job listings saved for each keyword/location/category combination.

## `includeDescription` (type: `boolean`):

Visit each job page to collect the full description, category, employment type, and remote flag. Adds one request and one add-on charge per job.

## `categories` (type: `array`):

Optional job category filters. Each selected category is searched separately.

## `experienceLevels` (type: `array`):

Optional experience filters. Selecting several returns jobs matching any of them.

## `salaryRanges` (type: `array`):

Optional annual salary band filters.

## `rentDiscounts` (type: `array`):

Optional filter on the employee rent-discount benefit advertised with the listing.

## `state` (type: `string`):

Optional exact state filter, e.g. "Texas".

## `city` (type: `string`):

Optional exact city filter, e.g. "Austin".

## `startUrls` (type: `array`):

Optional apartmentjobs.com job, search, company, city, state, or category URLs to scrape directly.

## `saveOnlyUniqueItems` (type: `boolean`):

Deduplicate listings by ApartmentJobs job ID across all searches.

## `maxPages` (type: `integer`):

Advanced limit for 20-item result pages per search.

## `requestDelaySecs` (type: `number`):

Seconds to wait between search result requests.

## `proxyConfiguration` (type: `object`):

US residential proxies are required: ApartmentJobs answers 403 to datacenter address ranges.

## Actor input object example

```json
{
  "keywords": "",
  "location": "",
  "maxItems": 100,
  "includeDescription": true,
  "categories": [],
  "experienceLevels": [],
  "salaryRanges": [],
  "rentDiscounts": [],
  "state": "",
  "city": "",
  "startUrls": [],
  "saveOnlyUniqueItems": true,
  "requestDelaySecs": 1,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `dataset` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("crabwalker/apartmentjobs-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("crabwalker/apartmentjobs-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 '{}' |
apify call crabwalker/apartmentjobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crabwalker/apartmentjobs-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/mEPGLgrIaYt0XU7jI/builds/yQgzecbvbK2U7zU6u/openapi.json
