# JustJoin.it Tech Job Scraper (`scrapyx/justjoinit-jobs-scraper`) Actor

Scrapes tech job offers from JustJoin.it, Poland's largest IT job board. Filter by location and one of 25 tech categories; returns salary by currency, required and nice-to-have skills, experience level, workplace type, company info, GPS coordinates and full job descriptions.

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

## Pricing

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

## JustJoin.it Tech Job Scraper (Poland)

Scrapes tech job offers from **[JustJoin.it](https://justjoin.it)** — Poland's
largest IT job board.

Public data only. No login, no cookies, no browser. **No bot challenge of any
kind** — 6 of 6 TLS profiles returned clean 200s on every surface, including a
bogus path (which answers a clean 404, not a challenge).

### The one thing you need to know before using this

**`page=` does nothing.** JustJoin.it's search page is client-side
infinite-scroll: the server always embeds the SAME freshest ~100 offers for a
given (location, category, keyword) combination, whatever page number you ask
for. There is no HTTP-only way to page past that — the client-side "load
more" call was searched for across all 45 Next.js chunks referenced by the
page and never found.

So this actor does not offer a page input at all. **Coverage comes from
diversifying the query**: run several `locations`, or narrow with `category`
/ `keyword`, rather than expecting one broad search to page through
everything. Each `SEARCH_SUMMARY` row reports `likelyMoreThanCap: true` when
a query returned the full 100-offer cap, as a signal that narrowing it further
would surface more.

### What you get

Three record types share one dataset, told apart by `recordType`.

#### `JOB` — one row per offer

Search rows already carry a rich structured summary: title, company (name +
logo), city, GPS coordinates, salary broken out **per currency** (PLN, EUR,
USD, GBP, CHF) and contract type, experience level, workplace type (remote /
hybrid / office), working time, required and nice-to-have skills, publish and
expiry dates, and the apply URL.

With **Fetch full job descriptions** on (the default), each row also gets
`jobPostingJsonLd` — JustJoin.it's Schema.org `JobPosting` record, verbatim,
with the full plain-text description.

#### `SEARCH_SUMMARY` — one row per location searched

The filters actually sent, how many offers came back, and whether this query
hit the 100-offer cap.

#### `ERROR` — one row per input that could not be processed

Every input maps to at least one row, so nothing disappears silently.

### Input

| Field | What it does |
| --- | --- |
| **Locations** | `all-locations`, or a city slug (`warszawa`, `wroclaw`, `krakow`…) — one search each |
| **Category** | one of 25 verified tech categories, or any |
| **Keyword** | free-text search box filter |
| **Max offers per location** | `0` = unlimited, meaning up to the ~100-offer cap per query |
| **Fetch full job descriptions** | full HTML→text description via one extra request per offer |
| **Min interval between requests** | the speed control — 0.3s default |

#### Example

```json
{
  "locations": ["all-locations", "warszawa", "wroclaw"],
  "category": "python",
  "keyword": "senior",
  "maxItems": 100,
  "includeJobDetails": true
}
```

### Notes on reliability

- **No WAF.** All surfaces answered 200 on `chrome124`, `chrome120`,
  `safari17_0`, `chrome99_android` and `firefox133`.
- **Location and category are both honest.** An unrecognised slug — alone or
  combined (`warszawa/bogus-category`) — answers a clean HTTP 404, never a
  silently widened baseline. A bogus keyword answers HTTP 200 with zero
  offers, also honest.
- **Speed is controlled by request rate, not concurrency**, same design as
  the other actors in this portfolio — `maxConcurrency` absorbs latency
  variance, `minRequestInterval` is the actual throttle.
- **Residential proxy pinned to Poland** is the cloud default.

### Known limits

- **~100 offers per (location, category, keyword) combination**, always the
  freshest ones — see "the one thing you need to know" above. There is no
  offset/cursor this actor can reach HTTP-only.
- The flight stream's own copy of the description is a chunked
  text-protocol reference this actor does not reassemble; the detail pass
  uses the page's JSON-LD block instead, which carries the same text
  unchunked. If you don't need the full description, list-only mode is one
  request per location regardless.

# Actor input Schema

## `locations` (type: `array`):

JustJoin.it location slugs — one search per entry, each with its own SEARCH_SUMMARY row. Use `all-locations` for the whole board, or a city slug (lowercase, hyphenated) such as `warszawa`, `wroclaw`, `krakow`, `gdansk`. An unrecognised slug answers a clean HTTP 404 (no silent-widen risk here — verified).

Each search returns only the freshest ~100 offers (see 'category'/'keyword' below for why) — several narrower locations return far more total data than one broad 'all-locations' call.

## `category` (type: `string`):

Restrict to one tech category, applied to every location in this run. An unrecognised slug returns a clean HTTP 404 (no silent-widen risk), and every value below was verified to genuinely narrow the results (checked alone and combined with a location).

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

Free-text search box filter, applied to every location in this run (e.g. 'senior', 'react', 'remote'). Verified to genuinely narrow results — a bogus keyword returns a clean zero offers rather than the unfiltered baseline.

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

Stop collecting a location's results after this many offers. Set to 0 for unlimited — note JustJoin.it itself never embeds more than ~100 offers per (location, category, keyword) combination in one page load, however many really exist, because `page=` is inert (see 'locations' above). Use a narrower category, keyword or location to reach more offers overall.

## `includeJobDetails` (type: `boolean`):

Fetch each offer's Schema.org JobPosting record via one extra request: the full plain-text job description, employment type and hiring organisation. Turn this off for a fast list-only crawl — list rows already carry title, company, city, coordinates, salary per currency, required/nice-to-have skills, experience level, workplace type and publish/expiry dates.

## `maxConcurrency` (type: `integer`):

How many requests may be in flight at once. This absorbs latency variance; it is NOT the throttle — the crawl rate is set by 'Minimum interval between requests' below.

## `minRequestInterval` (type: `number`):

The actual speed control: the shortest gap between two requests to JustJoin.it, across all workers. JustJoin.it serves no bot challenge at all, which is worth preserving — an unthrottled crawl is how a WAF-free site stops being WAF-free. Values below 0.05s are clamped.

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

JustJoin.it runs no bot challenge at all (6/6 TLS profiles clean on every surface), but Residential pinned to Poland is the cloud default — container egress is a different posture than a home connection.

## Actor input object example

```json
{
  "locations": [
    "all-locations"
  ],
  "category": "",
  "keyword": "",
  "maxItems": 100,
  "includeJobDetails": true,
  "maxConcurrency": 6,
  "minRequestInterval": 0.3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "PL"
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# 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 = {
    "locations": [
        "all-locations"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/justjoinit-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 = { "locations": ["all-locations"] }

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/justjoinit-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 '{
  "locations": [
    "all-locations"
  ]
}' |
apify call scrapyx/justjoinit-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapyx/justjoinit-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/8bleLx4ZzDWWMTMws/builds/83N23LcroSXzbDWu4/openapi.json
