# eJobs.ro Job Listings Scraper (`bovi/ejobs-ro-scraper`) Actor

Collect Romanian eJobs.ro job listings from the JSON API. Get job records with company, location, and descriptions. Run with keyword or filters; no live eJobs account required. Pricing: $0.0011 per dataset item on Free; $0.00005 per Actor Start event.

- **URL**: https://apify.com/bovi/ejobs-ro-scraper.md
- **Developed by:** [Vitalii Bondarev](https://apify.com/bovi) (community)
- **Categories:** Jobs, Lead generation
- **Features:** Limited permissions
- **Stats:** 15 total users, 8 monthly users, 88.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.07 / 1,000 ejobs.ro job listings scrapers

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

## eJobs.ro Job Listings Scraper

Collect public Romanian job listings from the `api.ejobs.ro` JSON API as Apify dataset records. No eJobs account is needed; billing details are in Pricing.

- Collect Romanian job data for recruiting and lead generation
- Filter by city, department, industry, career level, contract type, age, and verification
- Add full job, ideal-candidate, and company descriptions with `fetchDetails`
- Analyze company identifiers, locations, taxonomy values, dates, and source URLs

### What you get

Each job is one dataset record. Nullable fields are `null` when the source provides no value.

| Field(s) | Type | Contents | Availability |
|---|---|---|---|
| `id`, `url` | string; never `null` | ID and public URL; `id` is `""` when absent; a missing or null slug leaves the URL segment empty. | Listing |
| `slug` | string; `""` when omitted, `null` when sent as `null` | Slug. | Listing |
| `title` | string; `null` when absent | Job title. | Listing |
| `company`, `companySlug` | string; `null` when absent | Employer name and slug. | Listing |
| `companyId` | integer; `null` when absent | Employer ID. | Listing |
| `companyVerified` | boolean; `null` when absent | Verification value. | Listing |
| `companyLogoUrl` | string; `null` when absent | Absolute logo URL. | Listing |
| `locations` | array of objects; `[]` when none; fields can be `null` | `cityId`, `cityName`, `address`, `latitude`, `longitude`, `countyId`, `countyName`. | Listing |
| `location` | string; `null` when no city | First city name. | Normalized location |
| `cities`, `counties` | array of strings; `[]` when none | Normalized city and county names. | Listing |
| `positions` | integer; `null` when absent | Number of positions. | Listing |
| `contractTypesIds`, `careerLevelsIds`, `departmentsIds`, `industriesIds` | array of integers; `[]` when absent | Taxonomy IDs. | Listing |
| `contractTypes`, `careerLevels`, `departments`, `industries` | array of strings; `[]` when none | Names resolved from IDs. | Listing |
| `educationLevelsIds`, `languagesIds` | array of integers; `[]` when absent or details are off | Education and language IDs. | Detail; may be empty when off |
| `educationLevels`, `languages` | array of strings; `[]` when none or details are off | Education and language names. | Detail; may be empty when off |
| `creationDate`, `publishDate` | ISO 8601 string; `null` when absent | Creation and publication dates; same value. | Listing |
| `expirationDate` | ISO 8601 string; `null` when absent | Expiration value, possibly date-only. | Listing |
| `lastModified`, `republishDate` | ISO 8601 string; `null` when absent or details are off | Modification and republishing timestamps. | Detail; may be `null` when off |
| `jobType` | string; `null` when absent or details are off | Job-type value. | Detail; may be `null` when off |
| `idealCandidateHtml`, `descriptionHtml`, `companyDescriptionHtml` | string; `null` when not loaded | Descriptions in markup. | `fetchDetails` on and successful detail request |
| `idealCandidateText`, `descriptionText`, `companyDescriptionText` | string; `""` when not loaded | The same descriptions as readable text. | `fetchDetails` on and successful detail request |
| `summary` | string; `null` when not loaded | Short detail-response excerpt. | `fetchDetails` on and successful detail request |
| `source` | string; never `null` or empty | `ejobs.ro`. | Added by the Actor |
| `scrapedAt` | ISO 8601 string; never `null` or empty | Processing timestamp. | Added by the Actor |
| `detailStatus`, `detailError` | string; absent unless a detail request fails | `"failed"` and an error limited to 200 characters. | Detail request failure |

### Sample output

Shortened sample: selected fields from one real dataset record; omitted fields are whole fields from the same record. This record used `fetchDetails: false`, so `descriptionText` is empty.

```json
{
  "id": "99248571",
  "url": "https://www.ejobs.ro/user/locuri-de-munca/contabil/99248571",
  "title": "Contabil",
  "company": "PREMIER RESTAURANTS ROMANIA SRL",
  "companyId": 11389,
  "companyVerified": true,
  "location": "București",
  "cities": ["București"],
  "contractTypes": ["Full time"],
  "careerLevels": ["Senior-Level (> 5 ani)"],
  "departments": ["Financiar / Contabilitate"],
  "industries": ["Turism / HoReCa"],
  "publishDate": "2026-10-28T05:00:00+02:00",
  "descriptionText": "",
  "source": "ejobs.ro",
  "scrapedAt": "2026-10-28T06:35:59.403555+00:00"
}
```

### Pricing

You pay only for the events below. This Actor does not use the Pay per event + usage option, so compute, proxy traffic, and storage are not billed separately.

- **Actor Start**: charged when the Actor starts, with one event per GB of run memory and a minimum of one event.
- **eJobs.ro Job Listings Scraper**: one dataset-item event per scraped result.

| Apify plan | Actor Start (per event) | Dataset item (per event) |
|---|---:|---:|
| Free | $0.00005 | $0.0011 |
| Bronze | $0.0000495 | $0.001089 |
| Silver | $0.000049 | $0.001078 |
| Gold | $0.0000485 | $0.001067 |
| Platinum | $0.0000485 | $0.001067 |
| Diamond | $0.0000485 | $0.001067 |

On the Free plan: 1,000 dataset items = $1.10; 10,000 = $11.00; 1,000 starts = $0.05; 10,000 = $0.50; 10 results = $0.011.

At the Actor's default 512 MB memory, a default Free-plan run of 10 results costs $0.01105 ($0.00005 + 10 × $0.0011, one start event), so the $5 Free-plan monthly credit covers 452 full runs (4,520 results).

The `apify-default-dataset-item` event is one event per record written. Filtered listings, `maxAgeDays` exclusions, repeated job IDs within one run, unparseable listings, and empty pages create no dataset-item charge. A record with `detailStatus: "failed"` is delivered and charged.

The `job-record` event has no price, so you are not charged for it.

### How to use

1. Enter an optional `keyword` and set `maxResults`.
2. Turn `fetchDetails` on to fill the description fields; with it off they stay empty.
3. Add filters, `maxAgeDays`, or `verifiedOnly` when needed.
4. Start the run, inspect the dataset, and repeat it through Apify Schedules (daily suits job listings).

Example input:

```json
{
  "keyword": "contabil",
  "maxResults": 10,
  "fetchDetails": true,
  "cities": ["București"],
  "careerLevels": ["Senior-Level (> 5 ani)"],
  "verifiedOnly": true
}
```

Export the dataset as JSON or CSV, or read it through the Apify dataset API.

The Actor calls the public JSON API with curl_cffi; it does not launch a browser, use Playwright or Selenium, or scrape eJobs HTML pages. The default proxy uses the `RESIDENTIAL` group with Romania as the proxy country; custom settings use `proxyConfiguration`.

### Input parameters

| Parameter | What it does | Default |
|---|---|---|
| `keyword` | Optional keyword for the listing request. | `""` |
| `fetchDetails` | Requests detail data and descriptions. | `true` |
| `maxResults` | Maximum records; integers from 1 through 5000. | `100` |
| `cities` | City names or numeric IDs. | Not set |
| `departments` | Department names or numeric IDs. | Not set |
| `industries` | Industry names or numeric IDs. | Not set |
| `careerLevels` | Career-level names or numeric IDs. | Not set |
| `contractTypes` | Contract-type names or numeric IDs. | Not set |
| `maxAgeDays` | Keeps jobs with a parsed creation or publication date no older than the supplied days. | Not set |
| `verifiedOnly` | Keeps records marked verified. | `false` |
| `proxyConfiguration` | Configures Apify Proxy requests. | `useApifyProxy: true`, group `RESIDENTIAL`, country `RO` |
| `disableUsageStats` | Set to `true` to turn off the run summary described in Usage statistics. | `false` |

Each filter value must equal a full name or numeric ID; case and diacritics are ignored. A partial name such as Buc matches nothing. Values within one filter are alternatives, and different filters must all pass.

### Limits and edge cases

| Condition | What you see | What to do |
|---|---|---|
| Free-plan cap is reached. | Succeeded with at most 10 records and `stopReason: "PREVIEW_CAP"`; only delivered records are charged. | Use the preview or upgrade. |
| `maxResults` is below 1. | Failed with an empty dataset; `OUTPUT` contains `emptyReason` and `message`. | Use an integer from 1 through 5000. |
| A keyword returns HTTP 404 on page 1 after a second try, or no listings are returned. | Succeeded with an empty dataset; `OUTPUT` contains `emptyReason: "NO_MATCH_FOR_INPUT"` and `message`. | Check spelling, broaden the keyword, or remove it. |
| Filters, `maxAgeDays`, or `verifiedOnly` exclude every scanned listing. | Succeeded with an empty dataset; `OUTPUT` contains `emptyReason: "FILTERED_OUT"`. With an active filter, up to 150 pages of 50 are scanned; otherwise `ceil(maxResults / page size) + 5`. | Loosen or remove filters. |
| A listing has no usable date while `maxAgeDays` is set. | It does not pass the age filter. | Remove `maxAgeDays` or use a value supported by the dates. |
| A detail request fails after two attempts. | The record is delivered and charged with `detailStatus: "failed"` and `detailError` cut to 200 characters. | Use listing fields or rerun with `fetchDetails` off. |
| Before any record is delivered, a block, source error, taxonomy failure, time limit, or total parse failure occurs. | The run ends Failed with an empty dataset; `OUTPUT` contains an `emptyReason` (`BLOCKED`, `UNKNOWN`, or `PARSE_FAILED`) and `message`; nothing is charged. | Check the source and proxy, retry, lower `maxResults`, or turn `fetchDetails` off. |
| Records are being collected. | Records reach the dataset in batches of 10. | Inspect the dataset and `OUTPUT`. |
| A block, source error, HTTP 404, page limit, or time limit occurs after delivery begins. | Succeeded with partial results; `OUTPUT` carries `totalItems`, `message`, `stoppedEarly`, and `stopReason`. | Run again later for the rest. |
| The run is aborted or killed. | Written records stay; up to 9 unwritten records are lost. Unwritten records are not charged. | Rerun the same input. |

### FAQ

#### How much does it cost to scrape Romanian job listings?

$0.0011 per result on the Free plan; 10 results = $0.011. See Pricing for all plans.

#### Why did the run return no records?

The keyword may have no matches, filters may exclude every listing, or a missing usable date may fail `maxAgeDays`. `OUTPUT` explains the empty condition.

#### Is using this public eJobs data legal or allowed?

The Actor collects data from the public `api.ejobs.ro` JSON API. Whether a use is legal or allowed depends on source terms, applicable law, and your use case; review those requirements before using the data. The Actor provides no legal guarantee.

### Related actors

- [AIJobs.net Scraper](https://apify.com/bovi/aijobs-net-scraper) — related job-listing Actor for AIJobs.net.
- [Arbeitsagentur Jobs Scraper](https://apify.com/bovi/arbeitsagentur-jobs-scraper) — related Actor for German jobs through an API.
- [Ashby Jobs API Scraper](https://apify.com/bovi/ashby-job-scraper) — related Actor for Ashby job listings through an API.

### Usage statistics

This Actor creates a small, content-free summary at the end of each run, used only to monitor reliability and improve this Actor. No input values, credentials or scraped data are sent.

A copy of the exact record for your run is saved as `USAGE_STATS` in your own Apify key-value store.

To opt out, set `disableUsageStats` to `true` in the input. Nothing is sent then.

[Full list of collected fields](https://api.apify.com/v2/key-value-stores/v9rgHjMKLDGtHf8tD/records/index.html)

# Actor input Schema

## `keyword` (type: `string`):

Optional keyword used for searching jobs. Filters are applied after records are normalized.

## `fetchDetails` (type: `boolean`):

When enabled, fetching details adds the full job description, ideal-candidate description, and company description. Filters are applied after records are normalized.

## `maxResults` (type: `integer`):

Maximum number of job records to return.

## `cities` (type: `array`):

Optional city filters. Names or numeric IDs are accepted. Filters are applied after records are normalized.

## `departments` (type: `array`):

Optional department filters. Names or numeric IDs are accepted. Filters are applied after records are normalized.

## `industries` (type: `array`):

Optional industry filters. Names or numeric IDs are accepted. Filters are applied after records are normalized.

## `careerLevels` (type: `array`):

Optional career-level filters. Names or numeric IDs are accepted. Filters are applied after records are normalized.

## `contractTypes` (type: `array`):

Optional contract-type filters. Names or numeric IDs are accepted. Filters are applied after records are normalized.

## `maxAgeDays` (type: `integer`):

Optional maximum age of jobs in days. This filter is applied after records are normalized.

## `verifiedOnly` (type: `boolean`):

Return only verified jobs. This filter is applied after records are normalized.

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

Enables Apify Proxy with the RESIDENTIAL group and Romania as the proxy country.

## `disableUsageStats` (type: `boolean`):

See the README Usage statistics section for details.

## Actor input object example

```json
{
  "keyword": "",
  "fetchDetails": true,
  "maxResults": 100,
  "verifiedOnly": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "RO"
  },
  "disableUsageStats": false
}
```

# Actor output Schema

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

Dataset containing eJobs.ro job records (id, title, company, location, contractTypes, careerLevels, departments, industries, publishDate, description, idealCandidate, companyDescription, url, scrapedAt).

# 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("bovi/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 = {}

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

```

## MCP server setup

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