# eJobs.ro Scraper (`crawlerbros/ejobs-ro-scraper`) Actor

Scrape eJobs.ro, Romania's largest job board. Search by keyword, browse by city/county, browse by industry/department, or pull every open listing for a specific company. Get title, company, location, contract type, career level, industry, department, salary and posting dates.

- **URL**: https://apify.com/crawlerbros/ejobs-ro-scraper.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** Automation, Lead generation, Developer tools
- **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 and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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

## eJobs.ro Scraper

Scrape **eJobs.ro** — Romania's largest online job board. Search by keyword, browse jobs by
city or county, browse by industry / job department, pull every open listing for a specific
company, or look up specific jobs directly by URL — with filters for contract type, career
level, and education level. Get company details, salary (when published), industry/department,
posting and expiration dates, and the direct link to every listing. HTTP-only, no login, no
proxy required.

### What this actor does

- **Five modes:** `search` (free-text keyword), `byLocation` (city or county), `byCategory`
  (industry or department), `byCompany` (every open listing for one employer), `byUrl` (look up
  one or more specific jobs directly by their eJobs.ro URL)
- **Combinable filters & sort:** `search`/`byLocation`/`byCategory` can each be refined with
  contract type, career level, education level, city, county, industry, department, and
  salary-disclosed-only — all at once — plus a choice of relevance (default) or newest-first
  sort order
- **Full Romania coverage:** all 41 counties + Bucharest, 41 major cities, remote jobs, and
  jobs abroad
- **Optional enrichment:** `fetchFullDescription` adds the full job description, candidate
  requirements, education/language requirements, and last-updated date for each job (one
  extra request per job); `byCompany` runs always include the employer's profile description,
  cover photo, Facebook page, and HQ address at no extra cost; `outputLanguage` (`ro`/`en`)
  switches the display text of the four finite-value facet fields
- **Empty fields are omitted** — a field only appears on a record when eJobs.ro actually
  published a value for it (e.g. `salary` only shows up when the employer disclosed one)

### Output per job

- `jobId`, `title`
- `companyName`, `companyId`, `companyUrl`, `companyLogoUrl`, `companyVerified`
- `location` — human-readable city/county text as shown on eJobs.ro (joins multiple cities
  with a comma, or `Toate orașele` for nationwide listings)
- `cities[]` — the individual city names behind `location` (omitted for nationwide listings)
- `positions` — number of open positions for this listing
- `salary` — as published by the employer (omitted when not disclosed)
- `contractTypes[]` — e.g. `Full time`, `Part time`, `Internship / Voluntariat`
- `careerLevels[]` — e.g. `Entry-Level (< 2 ani)`, `Senior-Level (> 5 ani)`
- `industries[]` — e.g. `IT / Telecom`, `Comerț / Retail` (as tagged by the employer)
- `departments[]` — job function/department, e.g. `Financiar / Contabilitate`, `Vânzări`
- `hasVideo` — `true` when the listing includes a video ad
- `postedDate`, `expirationDate` — ISO 8601 UTC timestamps
- `externalApplyUrl` — when the employer routes applications off-platform, the external
  application link (omitted for the majority of listings that use eJobs.ro's own apply flow)
- `url` — the job posting's page on eJobs.ro
- `recordType: "job"`, `scrapedAt`

**Only when `fetchFullDescription: true` (one extra request per job):**

- `jobDescription` — the employer's full responsibilities/duties text, HTML stripped
- `idealCandidate` — the employer's full requirements/candidate-profile text, HTML stripped
- `companyDescription` — the "about the employer" text, when the employer wrote one, HTML
  stripped (also populated for free in mode=byCompany — see below)
- `educationLevels[]` — required education level(s) as published on the listing's own page,
  e.g. `Absolvent`, `Studii superioare`
- `languages[]` — required language(s), e.g. `engleză`, `franceză`
- `counties[]` — the județ/county the listing's cities belong to, e.g. `Timiș`
- `lastUpdatedDate` — ISO 8601 UTC timestamp of the listing's last edit/republish

**Only in mode=byCompany (from the employer's public company profile, no extra request):**

- `companyDescription` — the employer's "about us" text, HTML stripped
- `companyCoverUrl` — the company profile's cover photo
- `companyFacebookUrl` — the company's Facebook page, when linked on their profile
- `companyAddress`, `companyCity` — the employer's primary HQ address/city as published
- `companyLatitude`, `companyLongitude` — HQ coordinates, when eJobs.ro has them geocoded

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `search` | `search` / `byLocation` / `byCategory` / `byCompany` / `byUrl` |
| `searchQuery` | string | `manager` | Free-text keyword (mode=search, required; optional refine in byLocation/byCategory; unused in byCompany/byUrl) |
| `location` | string | – | Major city, `remote`, or `strainatate` (abroad) (mode=byLocation, required unless `county` set) |
| `county` | string | – | Romanian county / județ (mode=byLocation, required unless `location` set) |
| `industry` | string | – | Employer industry (mode=byCategory, required unless `department` set) |
| `department` | string | – | Job function / department (mode=byCategory, required unless `industry` set) |
| `contractType` | string | – | `full-time` / `part-time` / `internship` / `project-based` |
| `careerLevel` | string | – | Required seniority level |
| `educationLevel` | string | – | Required education level |
| `salaryDisclosedOnly` | boolean | `false` | Only return listings where the employer published a salary |
| `sortBy` | string | `relevance` | `relevance` or `newest` |
| `companyId` | integer | – | eJobs.ro's numeric company id (mode=byCompany, required) — copy from a prior run's `companyId`/`companyUrl` output field |
| `companySlug` | string | – | Cosmetic slug from `companyUrl` (mode=byCompany, optional — eJobs.ro resolves the company from `companyId` alone) |
| `jobUrls` | array of strings | `[]` | One or more direct eJobs.ro job-detail URLs (mode=byUrl, required) — the same format this actor's own `url` output field returns |
| `fetchFullDescription` | boolean | `false` | Also fetch each job's own detail page for `jobDescription`, `idealCandidate`, `companyDescription`, `educationLevels`, `languages`, `counties`, `lastUpdatedDate` (one extra request per job — slower, all modes) |
| `outputLanguage` | string | `ro` | `ro` or `en` — display-text language for the `contractTypes`/`careerLevels`/`industries`/`departments` facet fields only |
| `maxItems` | int | `40` | Hard cap on jobs returned (1–1000) |

#### Example: keyword search

```json
{
  "mode": "search",
  "searchQuery": "python developer",
  "contractType": "full-time",
  "maxItems": 50
}
```

#### Example: browse Cluj-Napoca IT jobs, newest first

```json
{
  "mode": "byLocation",
  "location": "cluj-napoca",
  "industry": "it---telecom",
  "sortBy": "newest",
  "maxItems": 100
}
```

#### Example: browse an entire county by industry, entry-level only

```json
{
  "mode": "byCategory",
  "industry": "medicina---sanatate",
  "county": "timis-judet",
  "careerLevel": "entry-level",
  "maxItems": 50
}
```

#### Example: remote sales jobs

```json
{
  "mode": "byLocation",
  "location": "remote",
  "department": "vanzari",
  "maxItems": 40
}
```

#### Example: jobs with a published salary only

```json
{
  "mode": "search",
  "searchQuery": "contabil",
  "salaryDisclosedOnly": true,
  "maxItems": 40
}
```

#### Example: every open listing at one company

```json
{
  "mode": "byCompany",
  "companyId": 314533,
  "companySlug": "profi-rom-food-srl",
  "maxItems": 100
}
```

#### Example: look up specific jobs by URL

```json
{
  "mode": "byUrl",
  "jobUrls": [
    "https://www.ejobs.ro/user/locuri-de-munca/branch-manager-targu-mures/1974317",
    "https://www.ejobs.ro/user/locuri-de-munca/contabil-senior/1978711"
  ]
}
```

#### Example: with full job description text

```json
{
  "mode": "search",
  "searchQuery": "python developer",
  "fetchFullDescription": true,
  "maxItems": 20
}
```

#### Example: English facet labels

```json
{
  "mode": "byLocation",
  "location": "bucuresti",
  "outputLanguage": "en",
  "maxItems": 40
}
```

### Use cases

- **Job-market research** — track hiring demand by city, county, or industry across Romania
- **Recruitment agencies** — build a live feed of openings in a specific department or career
  level
- **Salary benchmarking** — collect published salary ranges for a role or industry
- **Lead generation** — find actively hiring companies in a target industry or region
- **Labor-market dashboards** — feed posting volumes and contract-type mix into analytics

### FAQ

**What is the data source?**
This actor reads the public job-listing pages of eJobs.ro (`ejobs.ro`). It is an independent,
third-party scraper — not affiliated with or endorsed by eJobs.ro.

**Do I need to log in or provide cookies?**
No. All fields in the output are publicly visible on eJobs.ro's search-result pages without
an account.

**Why do some jobs have no `salary` field?**
eJobs.ro only publishes a salary range when the employer chooses to disclose one. When it's
missing, the field is omitted from the record rather than sent as `null` or an empty string.

**Why do some jobs have `cities` omitted but a `location` of "Toate orașele"?**
That's eJobs.ro's own label for listings open to candidates from any city in Romania
(nationwide remote-eligible or multi-city roles) — there is no single city to report.

**Can I combine city and industry filters?**
Yes — every filter field (`location`, `county`, `industry`, `department`, `contractType`,
`careerLevel`, `educationLevel`, `salaryDisclosedOnly`) can be combined with the `search`,
`byLocation`, and `byCategory` modes and with each other in a single run. `byCompany` returns
a company's full open-job list as-is and ignores these filters (eJobs.ro's own company page
has no filter controls).

**How do I find a `companyId` for mode=byCompany?**
Every job record from `search`/`byLocation`/`byCategory` already includes `companyId` and
`companyUrl` (`ejobs.ro/company/<slug>/<id>`) — run one of those modes first, then feed the
`companyId` you want into a `byCompany` run.

**How fresh is the data?**
Each run fetches eJobs.ro's live search-result pages at request time — there is no caching.

**What happens if a `jobUrls` entry has expired or was removed?**
That URL is skipped — eJobs.ro redirects a gone/expired job's detail page rather than serving
its data, so no record is produced for it. The run's status message reports how many of the
given URLs actually resolved to an active listing vs how many didn't (expired/removed, or
weren't a valid eJobs.ro job-detail URL to begin with). Nothing is fabricated for a missing job.

**Does `fetchFullDescription` slow the run down?**
Yes — it adds one extra HTTP request per job (the job's own detail page), so a run with
`maxItems: 100` makes roughly twice as many requests as the same run with
`fetchFullDescription` off. Leave it off for fast listing-only scrapes and turn it on only
when you need the full description text.

**Why do only `byCompany` runs get `companyDescription`/`companyCoverUrl`/`companyAddress`?**
Those fields live on eJobs.ro's company-profile page, not on individual job-listing pages.
`byCompany` mode already fetches that page to list the company's jobs, so this actor attaches
the same data to every job at no extra cost. `search`/`byLocation`/`byCategory` don't fetch a
company page per job (that would mean one extra request per unique employer per run), so those
modes omit these fields — run `byCompany` with the `companyId` from any job record if you need
an employer's profile details.

**Does `outputLanguage: "en"` translate the whole record?**
No — only `contractTypes`, `careerLevels`, `industries`, `departments`, and the "all cities"
location label are translated (eJobs.ro maintains both languages for these facets). Job
titles, descriptions, salary text, and city/county names stay exactly as the employer or
eJobs.ro published them — this actor never machine-translates free text.

**Which job-search filters does this actor cover?**
Every filter axis eJobs.ro exposes on its public pages without a login: keyword, city, county,
industry, department, contract type, career level, education level, salary-disclosed, sort
order, per-company job listings, direct job-URL lookup, and output language (`ro`/`en`).

**Are there any filters or fields this actor intentionally leaves out?**
Yes, a few things that aren't part of eJobs.ro's public job-search surface:

- **Minimum/maximum salary range.** eJobs.ro only exposes a binary "salary disclosed or not"
  checkbox (`salaryDisclosedOnly` here) — there's no numeric min/max range to filter by.
- **CV/candidate-profile facets** (language level, skill level, education *type* such as
  "Facultate" / "Masterat" / "MBA") power eJobs.ro's candidate-profile builder, not job search
  — applying them to a job search redirects to the homepage, so they were left out rather than
  offered as a filter that silently returns unfiltered results.
- **Filtering `byCompany` results.** A company's page has its own pagination but no filter
  controls, so `byCompany` always returns the company's complete open-job list unfiltered —
  use `search`/`byLocation`/`byCategory` for filtered results.
- **Company search / directory browsing.** eJobs.ro has no public "browse all companies" page
  — `byCompany` requires a `companyId` obtained from a prior job record (see above).
- **"Similar jobs" recommendations and a job's `jobType`/`republishDate` fields.** These add no
  value here: `similarJobs` duplicates records this actor already returns for real, and every
  row this actor produces is by construction an active listing with a real `lastUpdatedDate`.
- **Login-only features** (saved searches, job alerts, applying, CV upload) — this actor only
  reads publicly visible listing data, no account is required or used.

**Is there a limit on how many jobs I can scrape per run?**
`maxItems` accepts 1–1000. Set it based on how many results your `searchQuery` / filter
combination realistically returns. Note that eJobs.ro's own search-result pagination is
capped independently of the "N Joburi" total shown on the page header — broad queries
(e.g. a single common word) typically stop serving new pages around 400–500 listings even
when the header advertises 700+. When this happens the run's status message reports how many
records were actually delivered vs requested, so it's never a silent, unexplained shortfall.

# Actor input Schema

## `mode` (type: `string`):

What to fetch: free-text keyword search, browse by city/county, browse by industry/department, list a specific company's open jobs, or look up one or more jobs directly by URL.

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

Free-text keyword to search job titles for, e.g. `python`, `contabil`, `inginer constructii` (mode=search, required; optional refine in modes byLocation/byCategory; not used in modes byCompany/byUrl).

## `companyId` (type: `integer`):

eJobs.ro's internal numeric company id (mode=byCompany, required). Every job record this actor returns already includes `companyId` and `companyUrl` — copy the id from a prior run's output, e.g. `314533` for the example below, or from the company's URL `ejobs.ro/company/<slug>/<id>`.

## `companySlug` (type: `string`):

Cosmetic URL slug from `companyUrl`, e.g. `profi-rom-food-srl` (mode=byCompany, optional). eJobs.ro resolves the company purely from `companyId`, so this can be left blank — any value (or none) still lands on the correct company page.

## `jobUrls` (type: `array`):

One or more direct eJobs.ro job-detail URLs to look up, e.g. `https://www.ejobs.ro/user/locuri-de-munca/<slug>/<id>` — the same URL format this actor's own `url` output field returns (mode=byUrl, required; unused in other modes). Each URL is fetched once and returns a single, fully-populated record — including the full description fields that other modes only add when `fetchFullDescription` is on, at no extra cost since the page is already fetched.

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

Browse jobs in a specific major city, remotely, or abroad (mode=byLocation, required unless `county` is set; optional refine in other modes; not used in modes byCompany/byUrl).

## `county` (type: `string`):

Browse jobs across an entire Romanian county (mode=byLocation, required unless `location` is set; optional refine in other modes; not used in modes byCompany/byUrl).

## `industry` (type: `string`):

Browse jobs by employer industry (mode=byCategory, required unless `department` is set; optional refine in other modes; not used in modes byCompany/byUrl).

## `department` (type: `string`):

Browse jobs by job function / department (mode=byCategory, required unless `industry` is set; optional refine in other modes; not used in modes byCompany/byUrl).

## `contractType` (type: `string`):

Filter by employment contract type (modes search/byLocation/byCategory, optional; not used in modes byCompany/byUrl).

## `careerLevel` (type: `string`):

Filter by required seniority / experience level (modes search/byLocation/byCategory, optional; not used in modes byCompany/byUrl).

## `educationLevel` (type: `string`):

Filter by required education level (modes search/byLocation/byCategory, optional; not used in modes byCompany/byUrl).

## `salaryDisclosedOnly` (type: `boolean`):

Only return listings where the employer published a salary or salary range (eJobs.ro's own "Salarii" filter). Modes search/byLocation/byCategory, optional; not used in modes byCompany/byUrl.

## `sortBy` (type: `string`):

Result order: relevance (eJobs.ro's default ranking) or newest first. Not used in mode=byCompany.

## `fetchFullDescription` (type: `boolean`):

Also fetch each job's own detail page for `jobDescription`, `idealCandidate` (requirements), `companyDescription`, `educationLevels`, `languages`, `counties`, and `lastUpdatedDate`. Adds one extra HTTP request per job, so runs take longer and rate-limit sooner — leave off for fast listing-only scrapes. Ignored in mode=byUrl, which already fetches each job's detail page and always includes these fields for free.

## `outputLanguage` (type: `string`):

Language for the `contractTypes`, `careerLevels`, `industries`, and `departments` label text (eJobs.ro publishes both — same underlying facet ids, different display text). Free-text employer content (title, salary, descriptions, city/county names) always stays in the language the employer wrote it in; this only translates those four finite-value facet labels.

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

Maximum number of job listings to return. In mode=byUrl this is naturally capped by the number of `jobUrls` supplied.

## Actor input object example

```json
{
  "mode": "search",
  "searchQuery": "manager",
  "companyId": 314533,
  "companySlug": "profi-rom-food-srl",
  "jobUrls": [],
  "location": "",
  "county": "",
  "industry": "",
  "department": "",
  "contractType": "",
  "careerLevel": "",
  "educationLevel": "",
  "salaryDisclosedOnly": false,
  "sortBy": "relevance",
  "fetchFullDescription": false,
  "outputLanguage": "ro",
  "maxItems": 40
}
```

# Actor output Schema

## `jobs` (type: `string`):

Dataset containing all scraped eJobs.ro job listings.

# 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 = {
    "mode": "search",
    "searchQuery": "manager",
    "companyId": 314533,
    "companySlug": "profi-rom-food-srl",
    "jobUrls": [],
    "salaryDisclosedOnly": false,
    "sortBy": "relevance",
    "fetchFullDescription": false,
    "outputLanguage": "ro",
    "maxItems": 40
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/ejobs-ro-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 = {
    "mode": "search",
    "searchQuery": "manager",
    "companyId": 314533,
    "companySlug": "profi-rom-food-srl",
    "jobUrls": [],
    "salaryDisclosedOnly": False,
    "sortBy": "relevance",
    "fetchFullDescription": False,
    "outputLanguage": "ro",
    "maxItems": 40,
}

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/ejobs-ro-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 '{
  "mode": "search",
  "searchQuery": "manager",
  "companyId": 314533,
  "companySlug": "profi-rom-food-srl",
  "jobUrls": [],
  "salaryDisclosedOnly": false,
  "sortBy": "relevance",
  "fetchFullDescription": false,
  "outputLanguage": "ro",
  "maxItems": 40
}' |
apify call crawlerbros/ejobs-ro-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlerbros/ejobs-ro-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/vdQCEKTANhgEBdRy2/builds/5Vyk1IopstzgM7oXu/openapi.json
