# NoFluffJobs Job Scraper (`axiomworks/nofluffjobs-jobs-scraper`) Actor

Searches NoFluffJobs listings by keyword, category, seniority, or city and returns job titles, companies, salary ranges, skills, locations, and remote status. Each distinct posting ID appears once per run, and requested salary currency and period are applied by the site.

- **URL**: https://apify.com/axiomworks/nofluffjobs-jobs-scraper.md
- **Developed by:** [Axiom Works](https://apify.com/axiomworks) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.19 / 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

## NoFluffJobs Jobs Scraper

**Returns:** one row per job with title, company, category, seniority, salary range, required skills, and remote status.
**Input:** `{"keyword":"python","categories":["backend"],"seniority":["senior"],"maxItems":20}`
**Best for:** tracking IT openings, comparing disclosed salaries, and building focused job research datasets.

```json
{"id":"senior-software-engineer-python-codilime-Remote","title":"Senior Software Engineer (Python)","company":"CodiLime","category":"backend","fullyRemote":true,"salaryFrom":20500,"jobUrl":"https://nofluffjobs.com/job/senior-software-engineer-python-codilime-remote"}
```

### What does NoFluffJobs Jobs Scraper do?

NoFluffJobs Jobs Scraper extracts public IT job listings from the NoFluffJobs search API. Give it a keyword, a category, a seniority level, a city, or a combination of filters. It returns a dataset with one row for each distinct job: when NoFluffJobs lists the same job once per location, the locations are merged into one row's `cities` list. A run stops at the requested `maxItems` count when enough unique postings exist.

The Actor reads the public search response directly. It does not sign in, submit applications, or read private account information. The API is an internal site endpoint and its response format may change. Each row includes `sourceUrl`, which identifies the search endpoint and page that supplied that row. The `id` comes from the listing itself and remains useful for comparing results from later runs.

Use this NoFluffJobs scraper when you need a repeatable collection of listings rather than a manual search page. The NoFluffJobs API response supplies structured location, salary, skill, and posting fields, so the output can be filtered and compared in a spreadsheet or a downstream program. Search filters are sent to the site; the Actor does not infer job suitability or fill gaps with invented data.

### What data can you get?

The dataset contains these fields. Some jobs do not disclose every detail. Those values appear as `null` or, for lists, an empty array. Salary amounts reflect the currency and period requested in the input; the site performs that conversion in the search response.

| Field | Meaning |
| --- | --- |
| `id` | Stable posting ID supplied by NoFluffJobs. |
| `title` | Job title in the search result. |
| `company` | Employer or recruiter name. |
| `category` | Listing category, such as backend. |
| `seniority` | Seniority labels supplied as an array. |
| `salaryFrom` | Lower salary amount when displayed. |
| `salaryTo` | Upper salary amount when displayed. |
| `salaryCurrency` | Currency attached to the salary. |
| `salaryPeriod` | Period attached to the salary. |
| `contractType` | Contract type attached to the displayed salary. |
| `mainTechnology` | Main technology named on the card. |
| `requiredSkills` | Skill labels from requirement tiles. |
| `fullyRemote` | Whether the posting is marked fully remote. |
| `cities` | Named cities associated with the posting, excluding the Remote location label. |
| `country` | Country name from a posting location when present. |
| `countryCode` | Country code of the posting location (for example `POL`), copied from the source. The source labels some Polish postings `Polish` instead of `Poland` in `country`, so use `countryCode` to filter reliably. |
| `postedAt` | Posting timestamp converted to ISO 8601. |
| `sourceUrl` | Search API URL and page that supplied the row. |

The search API can represent a role in several places. The Actor deduplicates by the listing ID, so a repeated ID contributes only one row and one result event in a run. Postings sharing a job reference are also merged, including their locations when they appear on the same search page. `cities` can therefore contain several cities even when the retained posting ID names one of them.

A missing salary is not a zero salary. The salary fields are null when the search response does not include a salary object or an amount. Similarly, an empty `requiredSkills` list means the result had no requirement tiles available in that response. The Actor does not visit a job detail page to retrieve additional descriptions, benefits, application details, or requirements.

### How to use

Set `remoteOnly` to `true` to collect listings explicitly marked fully remote. The default is `false`, preserving the existing search scope. The filter uses location metadata first and falls back to the top-level remote flag if the location flag is absent. This also corrects `fullyRemote` for listings where the top-level flag conflicts with the location flag and `workMode` says remote. The Actor never infers remote status from a title or city name. Filtering occurs before reference-based deduplication and before `maxItems`, so the limit counts matching unique jobs. A remote-only search can read additional API pages to find enough matching jobs. If no result is found within the first-result time budget, the run succeeds with zero items and guidance to narrow the search. No detail-page request is added.

Open the Actor input form and set the filters that describe the jobs you want. For a focused search, start with a keyword and one or two categories. `keyword` is optional: an empty keyword with a category can browse that category. Leave category, seniority, and city arrays empty when you do not want those restrictions. Set `maxItems` to the number of rows needed. Start the run, then download the dataset in JSON, CSV, or Excel from the run's output.

The prefilled example searches for Python jobs in the backend category at senior level. The keyword is matched by NoFluffJobs as a technology or title term, so a title may not contain the literal word; check `mainTechnology` and `requiredSkills`. Extra fields: `jobUrl` (public posting page), `reference`, `renewedAt` and `workMode`.

The Actor requests page 1 first and uses `totalPages` from the response for subsequent pages. It stops when it reaches `maxItems`, the last reported page, or an empty page. Requests are paced at approximately one request per second between pages. If the site responds with a rate limit, server error, CAPTCHA wall, or access block, requests are retried with backoff. When an Apify Proxy is configured, each retry uses a fresh proxy session. A persistent block or unexpected search response ends the run successfully with the jobs already saved, or zero results and a status message suggesting another attempt or Apify Proxy. Block detection checks visible page text rather than script contents, avoiding retries triggered solely by anti-bot script names.

### Input

All filters are optional. Defaults let the Actor browse public jobs in the Polish region. The input form prefills a narrower Python example for an immediately useful first run.

| Input | Type | Default | How it affects the search |
| --- | --- | --- | --- |
| `keyword` | string | Empty | Free-text search matched against technology and title, e.g. `python`. |
| `categories` | string array | Empty | Category slugs, e.g. `backend`, `devops`. |
| `seniority` | string array | Empty | Seniority levels, e.g. `senior`. |
| `cities` | string array | Empty | City names, e.g. `Warszawa`. |
| `region` | string | `pl` | Two-letter region code for the search endpoint. |
| `salaryCurrency` | string | `PLN` | Requested salary display currency. |
| `salaryPeriod` | string | `month` | Requested salary display period. |
| `remoteOnly` | boolean | false | Return only jobs marked fully remote; the limit counts matching unique jobs. |
| `maxItems` | integer | 50 | Maximum unique rows to return, from 1 through 10000. |
| `proxyConfiguration` | object | Direct connection | Apify Proxy settings for requests to NoFluffJobs. |

For a search with filters, use:

```json
{
  "keyword": "python",
  "categories": ["backend"],
  "seniority": ["senior"],
  "cities": [],
  "region": "pl",
  "salaryCurrency": "PLN",
  "salaryPeriod": "month",
  "maxItems": 20,
  "proxyConfiguration": {"useApifyProxy": false}
}
```

For a category browse, omit `keyword` and provide `categories`, for example `{"categories":["devops"],"maxItems":25}`. NoFluffJobs decides which results match each submitted filter. To make comparisons across runs, use the same region, currency, period, and filter values each time. A nonsense keyword can legitimately produce an empty dataset and a warning. In that case, broaden the term or remove a restrictive filter.

`salaryCurrency` accepts PLN, EUR, USD, GBP, or CHF. `salaryPeriod` accepts month, day, hour, or year. Seniority values can be trainee, junior, mid, senior, or expert. Category and city options follow the site's search vocabulary. The Actor validates input shapes and values that have fixed choices; the site validates the actual category and location search results.

### Output

This record is from a local run using the prefilled filters with `maxItems: 20`. It is a representative output item, not a promise that the same listing will remain available on future runs:

```json
{
  "id": "senior-software-engineer-python-codilime-Remote",
  "title": "Senior Software Engineer (Python)",
  "company": "CodiLime",
  "category": "backend",
  "seniority": [
    "Senior"
  ],
  "salaryFrom": 20500,
  "salaryTo": 27000,
  "salaryCurrency": "PLN",
  "salaryPeriod": "Month",
  "contractType": "b2b",
  "mainTechnology": "Python",
  "requiredSkills": [
    "Python",
    "AI",
    "Claude Code",
    "REST"
  ],
  "fullyRemote": true,
  "cities": [
    "Warszawa"
  ],
  "country": "Poland",
  "countryCode": "POL",
  "postedAt": "2026-09-26T22:00:35.850Z",
  "sourceUrl": "https://nofluffjobs.com/api/search/posting?salaryCurrency=PLN&salaryPeriod=month&region=pl&language=en-GB&page=1",
  "jobUrl": "https://nofluffjobs.com/job/senior-software-engineer-python-codilime-remote",
  "reference": "UJWOC1XS",
  "renewedAt": "2026-10-14T22:00:35.850Z",
  "workMode": "remote"
}
```

The `sourceUrl` is an API search page, not the public job detail page. This is deliberate: every output value comes from the search result object returned by that endpoint. The `id` lets you reconcile rows across repeated collections. The Actor groups place-specific variants by the source reference within each page, retains the first posting ID, merges their cities, and suppresses the same reference on subsequent pages.

### How much does it cost?

The Actor is charged per result pushed to the dataset. A run that returns fewer results than `maxItems` only has result events for the rows actually returned. Duplicate IDs and invalid records are not pushed or charged. A run with no matches returns no data rows. Check the Actor's Pricing tab for the current charge settings before running a large search.

`maxItems` is both a practical dataset size control and an upper bound on result events. The Actor checks the remaining item count before pushing another record. If an account spending limit is reached, it stops before saving an additional result. The requested item count is not a guarantee of availability: the site may have fewer matches or may reject a search temporarily.

### Use with AI agents (MCP)

Apify MCP can expose the Actor as a tool to an AI agent. Configure the MCP server URL with the Actor's Store path:

```text
https://mcp.apify.com?tools=axiomworks/nofluffjobs-jobs-scraper
```

An agent can pass the same JSON inputs shown above. The dataset provides structured fields for follow-up comparisons and summaries. The agent should treat null values as missing source information and should not assume a listing is still open without checking a current run.

Example prompts for an agent include “Find up to 20 senior backend postings in Poland and summarize the disclosed salary ranges by company,” “Collect DevOps postings and show the required skill labels that recur most often,” and “Compare the current job IDs with last week's dataset to identify newly observed postings.” Each prompt maps to fields actually returned by the Actor. For a salary comparison, ask the agent to keep currency and period consistent across the runs being compared.

The first example can use `{"keyword":"python","categories":["backend"],"seniority":["senior"],"maxItems":20}`. The DevOps example can use `{"categories":["devops"],"maxItems":50}`. A comparison across weeks requires the agent or your workflow to retain a previous dataset; the Actor itself produces the current search results and does not store a history of earlier runs.

### Integrations and API

The Actor can be started from the Apify API, the Apify SDK, or an automation platform that can call the API. Replace the token placeholder in your own environment. Do not place a live token in shared scripts or reports. The examples request the input described in this README and retrieve the run's dataset items.

Python example:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("axiomworks/nofluffjobs-jobs-scraper").call(run_input={
    "keyword": "python", "categories": ["backend"], "maxItems": 20
})
items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
print(len(items))
```

JavaScript example:

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('axiomworks/nofluffjobs-jobs-scraper').call({
    keyword: 'python', categories: ['backend'], maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

cURL example:

```bash
curl -X POST \
  'https://api.apify.com/v2/acts/axiomworks~nofluffjobs-jobs-scraper/run-sync-get-dataset-items' \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"keyword":"python","categories":["backend"],"maxItems":20}'
```

For ongoing collection, schedule a run with stable filter settings and store each exported dataset with its run date. Use `id` to compare observed postings. `postedAt` is the site's posting timestamp, not the time your collection ran. If you need the collection time, keep the run metadata alongside the dataset rather than interpreting `postedAt` as an observation timestamp.

### FAQ

**Does this include full job descriptions?** No. The Actor extracts fields in the search response. It does not visit detail pages, so the output contains the listed title, company, salary, skills, location, and related search fields only.

**Why can a job appear for a keyword that is absent from its title?** NoFluffJobs performs the search and determines matches. The Actor sends `rawSearch` but does not impose a second text match on the returned title. The search service may match other posting content.

**Why are salary fields empty on some jobs?** The source does not always disclose pay in its search object. Null means the relevant amount or salary detail was absent. It does not mean the amount is zero.

**Can I search outside Poland?** The `region` field sends a two-letter region code to the search endpoint. Availability and results depend on what NoFluffJobs serves for that code. Use the same region when comparing datasets.

**How are duplicates handled?** The Actor keeps one row per exact posting ID and shared job reference within a run. It merges locations for a shared reference on the same page, retains the first posting ID, and skips that reference on later pages.

**What happens if the site blocks a request?** The Actor retries eligible errors with backoff. If a proxy is configured, each retry uses a fresh session. Persistent errors end successfully with a clear status message when no jobs were saved; the Actor does not create placeholder job rows.

### Is it legal to scrape NoFluffJobs?

This Actor reads publicly available search results without logging in. Rules for using, retaining, and redistributing collected information depend on your location and purpose. Review the site's terms and applicable law for your use case. The Actor is an independent tool and is not affiliated with or endorsed by NoFluffJobs. It does not access private accounts or submit applications.

### Feedback

If a field becomes empty after a site change, provide a run link and a sample `id` in the Actor's feedback channel. Include the filters used, especially region, currency, and period, because those can change what the search API returns. For repeated blocks, describe whether a proxy was enabled and which error appeared in the run log. Those details help distinguish a source format change from a temporary request failure.

# Actor input Schema

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

Free-text job or technology query, e.g. "python" or "inżynier", up to 200 characters. Default empty browses jobs using the other filters. Filters combine with this query. No matches return 0 jobs successfully; remove a filter or try a broader term.

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

Job category slugs to include, e.g. \['backend'], \['frontend','devops']. Common values: backend, frontend, fullstack, devops, data, testing, mobile, security, ai. Empty = all categories. Combined with the other filters (AND).

## `seniority` (type: `array`):

Seniority labels as a string array, e.g. \["senior"] or \["junior","mid"]. Use trainee, junior, mid, senior or expert. Default \[] includes all levels; combines with keyword, categories and cities.

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

City names to include, e.g. \['Warszawa','Kraków','Wrocław']. Use the city name as NoFluffJobs spells it. Empty = all cities (remote included).

## `region` (type: `string`):

Two-letter NoFluffJobs country site to search, e.g. 'pl' (Poland, default), 'cz' (Czechia), 'sk', 'hu', 'nl'. Also sets which postings are listed.

## `salaryCurrency` (type: `string`):

Currency for displayed salary amounts, e.g. "EUR": PLN (default), EUR, USD, GBP or CHF. The site converts salaryFrom and salaryTo; this does not require jobs to disclose pay. Missing salary values remain null.

## `salaryPeriod` (type: `string`):

Salary display period, e.g. "hour": month (default), day, hour or year. Applies together with salaryCurrency to salary amounts; use the same currency and period when comparing runs.

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

Maximum unique jobs across the entire search and all pages, e.g. 20. Integer 1-10000, default 50. Counts jobs after remoteOnly filtering and deduplication; returns fewer when fewer match. This is one shared limit, not a limit per category or city.

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

Apify Proxy settings for NoFluffJobs requests. Default is a direct connection; enable Apify Proxy (e.g. {"useApifyProxy": true}) if a run reports being blocked.

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

Whether to keep only jobs explicitly marked fully remote, e.g. true. Default false includes every work arrangement. Combines with the other filters; maxItems counts matching unique jobs. If empty, try false or remove a search filter.

## Actor input object example

```json
{
  "keyword": "python",
  "categories": [
    "backend"
  ],
  "seniority": [
    "senior"
  ],
  "cities": [],
  "region": "pl",
  "salaryCurrency": "PLN",
  "salaryPeriod": "month",
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "remoteOnly": false
}
```

# Actor output Schema

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

Dataset with one item per result, in the order found.

# 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 = {
    "keyword": "python",
    "categories": [
        "backend"
    ],
    "seniority": [
        "senior"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("axiomworks/nofluffjobs-jobs-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 = {
    "keyword": "python",
    "categories": ["backend"],
    "seniority": ["senior"],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("axiomworks/nofluffjobs-jobs-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 '{
  "keyword": "python",
  "categories": [
    "backend"
  ],
  "seniority": [
    "senior"
  ],
  "maxItems": 20
}' |
apify call axiomworks/nofluffjobs-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,axiomworks/nofluffjobs-jobs-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/VVHffFCvaV24vKpHf/builds/5fSTaS9JcEuDhrgM2/openapi.json
