# JustJoin.it Scraper — Polish Tech & IT Jobs + B2B/UoP Salary (`nomad-agent/justjoinit-scraper`) Actor

\[$0.90/1K] Scrape live IT jobs from JustJoin.it, Poland's top tech board — all 23 categories. Title, company, city, per-contract B2B/UoP/mandate salary, tech stack, required + nice-to-have skills, remote status, geo-coords. For CEE developer sourcing, salary analytics and job boards.

- **URL**: https://apify.com/nomad-agent/justjoinit-scraper.md
- **Developed by:** [Nomad.Dev](https://apify.com/nomad-agent) (community)
- **Categories:** Jobs, Developer tools, Automation
- **Stats:** 10 total users, 5 monthly users, 73.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 job 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/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

## JustJoin.it Jobs Scraper — Polish Tech & IT Jobs

> **Claude / Codex skill to describe and setup this actor: [SKILL.md](https://github.com/Exdenta/OinkAIJobSearch/blob/main/skill/justjoinit-scraper/SKILL.md)**

Scrape current IT openings from JustJoin.it — Poland's biggest tech board — including salary ranges and tech stacks.

- **$0.90 per 1,000 jobs, no start fee** — the cheapest JustJoin.it Actor on the Apify Store
- **All 23 tech categories by default** — full-board coverage, not a single search query
- **Delta mode** (`onlyNewSinceLastRun`) — scheduled runs return only what's new since the last run
- **Per-contract B2B / UoP / mandate salary breakdown** with PLN / EUR / USD conversions
- **Keyword, city, salary, recency and exclusion filters** built in

### What JustJoin.it data does this scraper extract?

Each result is one flat JSON record per job posting:

| Field | Meaning |
|---|---|
| `title` | Job title as posted |
| `company` | Hiring company / organisation |
| `source` | Always `"justjoinit"` — useful when merging with other scrapers' output |
| `location` | Location / duty station (may include remote hints) |
| `city` | Primary city of the posting |
| `latitude` / `longitude` | Primary-location geo-coordinates for mapping |
| `workplaceType` | `remote` / `hybrid` / `office`, as reported by the source |
| `experienceLevel` | `junior` / `mid` / `senior` / `c_level` / `intern`, as reported by the source |
| `skills` | Comma-separated required skills (string form) |
| `requiredSkills` | Required skill names as an array |
| `niceToHaveSkills` | Nice-to-have skill names as an array (populated with `extractFullDetails`) |
| `salary` | Salary text for the first disclosed contract type (e.g. `"B2B 22 000–28 000 PLN"`) |
| `salaryByType` | **Per-contract-type salary breakdown** — one object per contract type (B2B / permanent / mandate / …) with `type`, `min`, `max`, `currency`, `period`, `gross` and PLN/EUR/USD-normalised `minPln`/`maxPln`/`minEur`/`maxEur`/`minUsd`/`maxUsd` |
| `salaryMin` / `salaryMax` | Raw numeric min/max for the primary contract type |
| `salaryCurrency` | Salary currency code (e.g. `pln`) |
| `salaryPeriod` | Salary period (`month` / `hour` / `day` / `year`) |
| `salaryGross` | Whether the primary range is gross (`true`) or net (`false`) |
| `categoryId` | JustJoin.it numeric tech-category id |
| `companyLogo` | Hiring company logo thumbnail URL |
| `url` | Direct link to the posting |
| `postedAt` | Posting date where the source provides it (ISO 8601) |
| `snippet` | Short description excerpt |
| `id` | Stable source-side identifier |
| `description` | Full job description text — only with `extractFullDetails` |
| `skillLevels` | Map of skill name → required proficiency level (e.g. `{"React": 4}`) — only with `extractFullDetails` |
| `languages` | Required languages (`[{"code": "en", "level": "C1"}]`) — only with `extractFullDetails` |
| `companySize` | Reported company size (e.g. `"501+"`) — only with `extractFullDetails` |

### How to scrape JustJoin.it with this Actor

1. Click **Try for free** / **Run** — no login to the target site, no cookies, no proxies to configure.
2. Adjust the input (keyword, filters, `maxItems`) or keep the defaults.
3. Run it and export the dataset as JSON, CSV or Excel, or read it over the [API](https://docs.apify.com/api/v2).

Run it from your own code:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("nomad-agent/justjoinit-scraper").call(run_input={"maxItems": 50})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], "—", item["company"], item["url"])
```

Or a single HTTP call that runs the Actor and returns items in one response:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/nomad-agent~justjoinit-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"maxItems": 50}'
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `categories` | array of ids | `[]` = all 23 categories | Tech category ids to fetch: 1 JavaScript, 2 HTML/CSS, 3 PHP, 4 Ruby, 5 Python, 6 .NET, 7 Scala, 8 C, 9 Mobile, 10 Testing, 11 DevOps, 12 Admin, 13 UX/UI, 14 PM, 15 Game, 16 Analytics, 17 Security, 18 Data, 19 Go, 20 Support, 21 ERP, 22 Architecture, 23 Other. Empty means every category — the source has no "all categories" query, so broad coverage means one request per category id; `maxItems` still caps the total returned. Narrow it to specific categories if you only want e.g. frontend roles. Ids may be numbers or strings — `[1, 5]` and `["1", "5"]` both work. |
| `keywords` | array of strings | `[]` | Keep only postings whose title or skills contain at least one of these keywords (case-insensitive). Leave empty for all postings. |
| `city` | string | `""` | Keep only postings whose city or listed locations contain this text (case-insensitive), e.g. `Warszawa`. Leave empty for all locations. |
| `experienceLevels` | array (select) | `[]` | Filter by experience level (`junior` / `mid` / `senior` / `c_level` / `manager`, plus anything else the board uses, e.g. `intern`). The legacy `c-level` spelling is accepted and folded to `c_level`. Leave empty for all levels. |
| `workplaceTypes` | array (select) | `[]` | Filter by workplace arrangement (`remote` / `hybrid` / `office`). Leave empty for all types. |
| `contractTypes` | array (select) | `[]` | Keep only postings offering at least one of these contract types (`b2b` / `permanent` / `mandate_contract` / `internship` / `any`). Leave empty for all. |
| `withSalaryOnly` | boolean | `false` | Keep only postings that disclose a salary range. |
| `salaryMin` | integer | `0` | Drop postings whose top disclosed salary is below this (PLN-normalised). Postings with no salary are dropped when set. 0 disables. |
| `postedSince` | integer | `0` | Drop postings older than this many days, based on the source's publish date. Postings with a missing/unparseable publish date always pass through. Set 0 to disable. |
| `titleExclude` | array of strings | `[]` | Drop postings whose title contains any of these substrings (case-insensitive). |
| `companyExclude` | array of strings | `[]` | Drop postings whose company name contains any of these substrings (case-insensitive). |
| `maxItems` | integer | `100` | Maximum number of job postings to return across all requested categories. Set 0 for no limit. |
| `maxPages` | integer | `5` | Maximum number of result pages to fetch for each category (50 results per page). Increase to get older postings. |
| `extractFullDetails` | boolean | `false` | Advanced. Fetch each offer's page to add `description`, `skillLevels`, `languages` and `companySize`. One extra request per posting; fails open per posting. |
| `onlyNewSinceLastRun` | boolean | `false` | Incremental delta mode — emit only postings not returned by a previous run with the same filters. State persists across runs. |
| `stateKey` | string | `""` | Advanced. Override the delta bucket for `onlyNewSinceLastRun`; runs sharing a key share history. Empty = derive from filters. |
| `cacheTtlSeconds` | integer | `1800` | Reuse a recent fetch instead of hitting JustJoin.it again within this many seconds; set 0 to always fetch fresh data. |

### Output example

```json
{
  "id": "upvanta-senior-full-stack",
  "title": "Senior Full Stack Developer",
  "company": "Upvanta",
  "source": "justjoinit",
  "location": "Poznań (Remote)",
  "city": "Poznań",
  "latitude": 52.4064,
  "longitude": 16.9252,
  "workplaceType": "remote",
  "experienceLevel": "senior",
  "skills": "Node.js, Vue.js, Azure",
  "requiredSkills": ["Node.js", "Vue.js", "Azure"],
  "niceToHaveSkills": [],
  "salary": "B2B 22 000–28 000 PLN",
  "salaryByType": [
    {"type": "b2b", "min": 22000, "max": 28000, "currency": "pln", "period": "month", "gross": false, "minPln": 22000, "maxPln": 28000, "minEur": 5127.4, "maxEur": 6525.5, "minUsd": 5864.2, "maxUsd": 7463.5},
    {"type": "permanent", "min": 18000, "max": 23000, "currency": "pln", "period": "month", "gross": true, "minPln": 18000, "maxPln": 23000, "minEur": 4195.1, "maxEur": 5360.2, "minUsd": 4797.9, "maxUsd": 6130.4}
  ],
  "salaryMin": 22000,
  "salaryMax": 28000,
  "salaryCurrency": "pln",
  "salaryPeriod": "month",
  "salaryGross": false,
  "categoryId": 1,
  "companyLogo": "https://public.justjoin.it/companies/logos/original/upvanta.jpg",
  "url": "https://justjoin.it/job-offer/upvanta-senior-full-stack",
  "postedAt": "2026-06-26T09:00:27.819Z",
  "snippet": "level=senior; skills=Node.js, Vue.js, Azure; salary=B2B 22 000–28 000 PLN"
}
```

### Pricing

Pay per event: **$0.0009 per job returned** — 1,000 jobs ≈ **$0.90**, the cheapest JustJoin.it scraper on the Apify Store.
No start fee, no subscription, no rental — you pay only for what you fetch.

### Integrations

Export results as JSON, CSV or Excel; connect via Make, Zapier or n8n; call directly with `run-sync-get-dataset-items`; or plug into AI agents through the Apify MCP server.

### Use cases

- CEE tech job boards and alert bots
- Salary analytics for the Polish market (B2B vs UoP)
- Tech-stack demand tracking
- Sourcing developers in Poland

### FAQ

**Is it legal to scrape JustJoin.it?**
This Actor reads only publicly available job postings — data any visitor can see without logging in. No personal data behind authentication is touched. Review the target site's terms and your local regulations for your specific use case.

**Do I need an account on the target site?**
No. Postings are fetched from public pages/APIs — no login, cookies or session tokens.

**How fresh is the data?**
Every run fetches live listings. Results are cached for `cacheTtlSeconds` (default 30 min, set 0 to always hit the source live).

**How many jobs can I get?**
`maxItems` caps the run (set 0 where supported for no cap). Most sources paginate from newest to oldest.

**Something broken or missing?**
Open an issue on the Actor's **Issues** tab — it is monitored and reliability fixes ship fast.

**Is this Actor useful to you?**
A short review on the Actor's **Reviews** tab helps other users find it — it takes a minute and is genuinely appreciated.

### Related Actors

- [Web Developer Jobs Scraper — 10 Boards in One](https://apify.com/nomad-agent/web-dev-bundle)
- [NoFluffJobs Scraper — IT Jobs with Salaries](https://apify.com/nomad-agent/nofluffjobs-scraper)
- [Tecnoempleo Scraper — Spain IT & Tech Jobs](https://apify.com/nomad-agent/tecnoempleo-scraper)

***

**From the maker of [Oink](https://github.com/Exdenta/OinkAIJobSearch)** — an open-source, AI-powered job-search bot for Telegram that runs on these Actors. [Try the free bot](https://t.me/job_search_everyday_bot), get a managed instance at [oinkjobsearch.com](https://oinkjobsearch.com), or browse the [full catalog of 50+ Actors](https://apify.com/nomad-agent).

# Actor input Schema

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

Filter by JustJoin.it tech category id. Leave empty to fetch every one of the 23 categories — there's no server-side <code>all categories</code> mode, so broad coverage means looping over every id; <code>maxItems</code> still caps the total returned.<br>Ids: 1 JavaScript, 2 HTML/CSS, 3 PHP, 4 Ruby, 5 Python, 6 .NET, 7 Scala, 8 C, 9 Mobile, 10 Testing, 11 DevOps, 12 Admin, 13 UX/UI, 14 PM, 15 Game, 16 Analytics, 17 Security, 18 Data, 19 Go, 20 Support, 21 ERP, 22 Architecture, 23 Other.<br>Numbers and strings are both accepted (<code>\[1, 2]</code> and <code>\["1", "2"]</code>) — the item type is deliberately unconstrained so pre-2026-07 saved Tasks, Schedules and API calls (which send integers) are not rejected by input validation before the Actor starts.

## `keywords` (type: `array`):

Keep only postings whose title or skills contain at least one of these keywords (case-insensitive substring match). Leave empty to keep all postings.

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

Keep only postings whose city or listed locations contain this text (case-insensitive). Fully remote postings with no city are dropped when this is set. Leave empty for all locations.

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

Filter by experience level. Leave empty for all levels. Values are suggestions, not a closed list: any level the board uses is accepted (e.g. <code>intern</code>), and the legacy hyphen spelling <code>c-level</code> is folded to <code>c\_level</code> by the Actor.

## `workplaceTypes` (type: `array`):

Filter by workplace arrangement. Leave empty for all types.

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

Keep only postings offering at least one of these contract types. JustJoin.it quotes a distinct salary per contract type (see the <code>salaryByType</code> output field). Leave empty for all contract types.

## `withSalaryOnly` (type: `boolean`):

Keep only postings that disclose a salary range. Off by default.

## `salaryMin` (type: `integer`):

Drop postings whose top disclosed salary is below this amount, compared on a PLN-normalised basis (the source ships PLN conversions for EUR/USD ranges). Postings with no disclosed salary are dropped when this is set. Set 0 to disable.

## `postedSince` (type: `integer`):

Drop postings older than this many days, based on the source's publish date. Postings with a missing or unparseable publish date always pass through (never dropped). Set 0 to disable this filter.

## `titleExclude` (type: `array`):

Drop postings whose title contains any of these substrings (case-insensitive). Leave empty to keep all titles.

## `companyExclude` (type: `array`):

Drop postings whose company name contains any of these substrings (case-insensitive). Leave empty to keep all companies.

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

Maximum number of job postings to return across all requested categories. Set 0 for no limit.

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

Maximum number of result pages to fetch for each category (50 results per page, capped at 20 pages). Increase to get older postings.

## `extractFullDetails` (type: `boolean`):

Fetch each offer's page to add the full job <code>description</code>, per-skill proficiency levels (<code>skillLevels</code>), required <code>languages</code> and <code>companySize</code>. Adds one extra request per posting (slower); off by default. Fails open — a posting whose detail page can't be read is still returned with its base fields.

## `cacheTtlSeconds` (type: `integer`):

Reuse a recent fetch instead of hitting JustJoin.it again. Runs started within this many seconds of a previous run reuse the cached results; set 0 to always fetch fresh data.

## `onlyNewSinceLastRun` (type: `boolean`):

Incremental / delta mode. Emit only postings not already returned by a previous run of this Actor with the same filters, so scheduled runs surface just what's new. State persists across runs in a named key-value store; the very first run returns everything.

## `stateKey` (type: `string`):

Override the bucket used by <code>onlyNewSinceLastRun</code>. Runs sharing a key share delta history. Leave empty to derive it automatically from the selected filters.

## Actor input object example

```json
{
  "categories": [
    "1",
    "2",
    "18"
  ],
  "keywords": [
    "react",
    "aws"
  ],
  "city": "Warszawa",
  "withSalaryOnly": false,
  "salaryMin": 0,
  "postedSince": 0,
  "titleExclude": [],
  "companyExclude": [],
  "maxItems": 100,
  "maxPages": 5,
  "extractFullDetails": false,
  "cacheTtlSeconds": 1800,
  "onlyNewSinceLastRun": false
}
```

# 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("nomad-agent/justjoinit-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("nomad-agent/justjoinit-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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 nomad-agent/justjoinit-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=nomad-agent/justjoinit-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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