# Authentic Jobs Listings Scraper (`pradio/authentic-jobs`) Actor

Scrape job listings from authenticjobs.com, the job board for designers, developers and creative tech roles: title, company, location, job type, posting date, listing URL, company logo and tagline, one row per listing. No browser, no login.

- **URL**: https://apify.com/pradio/authentic-jobs.md
- **Developed by:** [E A](https://apify.com/pradio) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 listing returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Authentic Jobs Listings Scraper

Reads the live job listings on **authenticjobs.com** — the job board where employers post design, development and creative-tech roles — and returns one row per listing: the job title, the company, the location, the job type, the posting date, the link to the listing, the company logo, the company's one-line tagline, the board's own "Posted N ago" wording and the board's numeric id for the listing.

Type a search term such as `designer`, press Start, and a few seconds later you have a dataset you can download as JSON, CSV or Excel, read through the Apify API, or push into a spreadsheet, a database or an automation.

### Why this exists

Authentic Jobs has been running for years and the companies that post there are the ones design and engineering candidates recognise. The board has no public API and no export button: to get its listings out you either copy them by hand or read its pages yourself. People do search for a way in — "authentic jobs api" is a phrase that gets typed — and until now nothing on the Apify Store answered it.

This Actor reads the board the same way the board's own listing page reads it: through the request the page makes for its rows, one page at a time, honouring the site's stated crawl delay. No browser is launched, no account is needed and no proxy is used. It does one thing, it does it politely, and it tells you when it found nothing.

### What the board is

authenticjobs.com is a job board aimed at designers, front-end and back-end developers, product people and other creative and technical roles. Employers post the listings; each listing shows a company logo, the position, the company name and tagline, a location, a job type such as "Full-time" and how long ago it was posted. The full job description lives on the employer's own listing page on the board, one click away from the list.

At the time of writing the board holds on the order of a couple of hundred live listings. A search term narrows that. Listings stay up for months, so the posting date matters: the rows carry it both as an exact date and as the board's own relative wording.

### What one row looks like

A real row from a run, with the always-empty fields left out for brevity (every row does carry them, as `null` — see [Fields that are always empty on this board](#fields-that-are-always-empty-on-this-board)):

```json
{
  "platform": "authentic jobs",
  "official_url": "https://authenticjobs.com/job/36642/pinterest-principal-product-designer-ai-native-core/",
  "title": "Principal Product Designer, AI Native, Core",
  "posted_date": "2026-07-29",
  "location": "San Francisco, California US based applicants only (Remote)",
  "is_remote": true,
  "job_type": "Full-time",
  "company_name": "Pinterest",
  "company_logo": "https://authenticjobs.com/wp-content/uploads/job-manager-uploads/company_logo/2026/07/pinterest.png",
  "company_tagline": "Find inspiration for your life.",
  "posted_text": "Posted 1 month ago",
  "source_id": "36642",
  "result": "ROW"
}
```

Every value above was read from the board's listing row for that job. Nothing is inferred except `is_remote`, which is set to `true` when the word "remote" appears in the location or job-type text and is left `null` otherwise.

### How do I use Authentic Jobs Listings Scraper?

1. Open [the Actor's page on Apify](https://apify.com/Pradio/authentic-jobs) and press **Try for free** (or **Start** if you already have an account).
2. Enter a **keyword** — the term you would type into the search box on authenticjobs.com. `designer` is prefilled so a first run works without typing anything.
3. Optionally enter a **location**. The board matches it as text against the location line of each listing.
4. Optionally lower or raise **maximum items**. The default is 100 rows; the board itself rarely holds more than a few hundred listings in total.
5. Press **Start**. The run reads the board's listing pages, waits three seconds between pages as the site asks, and finishes in well under a minute for a full read.
6. Open the **Output** tab to browse the rows, or the **Storage** tab to download them as JSON, CSV, Excel or XML.

#### Example input

```json
{
  "keyword": "designer",
  "maxItems": 100
}
```

#### Running it from your own code

Every Apify Actor can be started through the API. This starts a run, waits for it to finish and returns the rows:

```bash
curl -X POST "https://api.apify.com/v2/acts/Pradio~authentic-jobs/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "keyword": "engineer", "maxItems": 50 }'
```

The same call is available from the Apify client libraries for JavaScript and Python, from the Apify MCP server, and from a **schedule** in the Apify Console if you want the board read every morning.

#### Reading the whole board

To read every listing rather than a search, use a broad keyword and a `maxItems` above the number of listings the board currently shows. The board's search decides what a keyword matches; this Actor passes your term through and does not filter the rows again on its side.

### Input

| Field | Type | Required | Default | What it does |
|---|---|---|---|---|
| `keyword` | string | yes | prefilled with `designer` | The search term, sent to the board's own search exactly as you typed it. What matches is decided by the board. |
| `location` | string | no | — | A place name, sent to the board's own location search and matched as text against each listing's location line. |
| `distance` | integer | no | — | **Ignored on this board.** Accepted so the input has the same shape as other job-board inputs, but authenticjobs.com cannot filter by distance. If you set it, the run log says it was ignored. |
| `maxItems` | integer | no | `100` | The most rows one run returns. Pages are read until the run holds that many; the rest of the board is not fetched. Minimum 1. |

#### keyword

This is the only required field. It goes straight into the board's search as the search keywords, so it behaves exactly as typing the same words into the search box on authenticjobs.com does. Try a role (`designer`, `engineer`, `product`), a technology, or a company name. Results come back newest first.

#### location

Sent as the board's location search. The board matches it against the free-text location an employer wrote — "San Francisco Bay Area", "Warsaw", "United States (Remote)" — so a country name will not find a listing whose location only names a city. Leave it empty to search every location.

#### distance

The field is accepted and then set aside. Authentic Jobs has no radius search, so there is nothing for a distance to do. The Actor logs a warning at the start of the run and carries on; the rows you get are the same as if you had left it blank.

#### maxItems

The board answers 50 listings per page. The Actor keeps requesting pages, three seconds apart, until it holds at least `maxItems` rows or the board reports no more pages, then returns at most `maxItems` rows after dropping any listing it has already seen. Lower it for a quick sample; raise it above the board's total to read everything.

### Output

Each data row carries 29 fields plus a `result` field that is always `"ROW"` on a data row. A value the board does not show is `null` — the field is always present, never missing, so a parser can rely on the shape.

#### Fields read from the listing

| Field | Type | Where it comes from |
|---|---|---|
| `platform` | string | Always `"authentic jobs"`. Lets you mix rows from several boards in one table and still know which is which. |
| `official_url` | string | The link on the listing row, e.g. `https://authenticjobs.com/job/36642/...`. Opens the full listing on the board. Rows are de-duplicated on this value. |
| `title` | string | The position heading on the listing row, e.g. "Product Designer, Growth". |
| `posted_date` | string | The machine-readable posting date the board attaches to the listing, as `YYYY-MM-DD`. |
| `location` | string | The location line of the listing, exactly as the employer wrote it: a city, a region, a country, sometimes with "(Remote)" or a note such as "US based applicants only". |
| `is_remote` | boolean | `true` when the location or job-type text contains the word "remote"; otherwise `null`. The board has no remote flag of its own, so a `null` means "the listing did not say", not "on site". |
| `job_type` | string | The job-type tag on the listing row, e.g. "Full-time". |
| `company_name` | string | The company name shown in bold on the listing row. |
| `company_logo` | string | The URL of the company logo image the board shows beside the listing, hosted on authenticjobs.com. |
| `company_tagline` | string | The one-line tagline the board shows under the company name. Shown exactly as the board shows it, which can be cut short for long taglines. |
| `posted_text` | string | The board's own relative wording for the posting date, e.g. "Posted 4 weeks ago". |
| `source_id` | string | The board's numeric id for the listing — the number in the listing URL, e.g. `36642`. Stable across runs, so it is a good key for tracking new and removed listings. |
| `result` | string | `"ROW"` on every data row. The other two values mark status rows, described below. |
| `reason` | string | Status rows only: one sentence saying why the run returned no rows, or why it stopped early. Absent from data rows. |
| `rowsFetched` | integer | Status rows only: how many listings the board handed over before duplicates were dropped and the cap applied. |
| `rowsReturned` | integer | Status rows only: how many data rows are in the dataset (`0` on an empty result). |
| `rowsRemaining` | integer | Status rows only: how many fetched listings were not returned (`0` on an empty result; the shortfall on `STOPPED_EARLY`). |

#### Fields that are always empty on this board

These fields exist so that rows from this board line up with rows from other job boards, but authenticjobs.com does not publish the value in its listings, so on this board they are `null` on every row:

| Field | Type | Why it is always `null` here |
|---|---|---|
| `description` | string | The board shows no description text in its listing rows; the description lives on the employer's listing page, which `official_url` links to. This Actor does not fetch those pages (see [Honest limits](#honest-limits)). |
| `job_level` | string | The board does not tag listings with a seniority level. |
| `job_function` | string | The board does not tag listings with a function or department. |
| `listing_type` | string | The board does not distinguish listing types. |
| `skills` | string | The board publishes no skills list in its listings. |
| `vacancy_count` | — | The board does not say how many openings a listing covers. Untyped in the schema because it is never filled here. |
| `experience_range` | string | The board publishes no years-of-experience range. |
| `salary_minimum` | number | The board does not publish salary. |
| `salary_maximum` | number | The board does not publish salary. |
| `salary_currency` | string | The board does not publish salary. |
| `company_industry` | string | The board shows no industry for the company. |
| `company_url` | string | The board's listing rows carry no link to the employer's own website. |
| `company_addresses` | string | The board shows no company address. |
| `company_revenue` | string | The board shows no company revenue. |
| `company_description` | string | The board shows no company profile text in its listing rows. |
| `company_rating` | number | The board has no company ratings. |
| `emails` | — | The board publishes no contact email in its listings. Untyped in the schema because it is never filled here. |

None of these will start filling in on their own. If a value you need is in this list, this is not the right source for it.

#### When the run finds nothing

A run that finds no results does **not** leave you with an empty dataset. It pushes exactly one status row so you can tell "nothing matched" from "something broke":

```json
{
  "result": "NO_MATCHING_LISTINGS",
  "reason": "the source answered and returned no rows for this input (0 fetched, 0 duplicate(s) dropped)"
}
```

This row means the board answered and had no listing for your keyword and location. It is not charged. If the board could not be read at all — a network error, an unexpected response, a changed page — the run **fails** instead, with the error in the log; it never reports a failure as "no results".

#### When the run stops early

If your account's spending limit for this Actor is reached mid-run, the Actor stops returning rows and pushes one final status row:

```json
{
  "result": "STOPPED_EARLY",
  "reason": "the run's charge limit was reached after 40 row(s); raise the limit and re-run to see the rest",
  "rowsReturned": 40,
  "rowsRemaining": 12
}
```

`rowsReturned` is how many data rows are in the dataset; `rowsRemaining` is how many listings were fetched but not returned. This row is not charged. A run that finishes normally never carries it, so a dataset ending in `STOPPED_EARLY` is always one that was cut short.

#### The run summary

Alongside the dataset, every run writes a `RUN_SUMMARY` record to its default key-value store with the counts a buyer needs to check a run without opening the rows:

```json
{
  "input": { "keyword": "designer", "maxItems": 100 },
  "rowsFetched": 57,
  "rowsPushed": 57,
  "rowsCharged": 57,
  "duplicatesDropped": 0,
  "stoppedEarly": false,
  "startedAt": "2026-09-02T18:20:11.000Z",
  "finishedAt": "2026-09-02T18:20:19.000Z",
  "source": "authentic jobs",
  "keyField": "official_url"
}
```

`rowsFetched` is what the board returned, `rowsPushed` is what reached the dataset, `rowsCharged` is what you paid for, and the three agree on a normal run. If the run was aborted from the Console, the summary carries `"aborted": true` and the counts stop at the moment of the abort.

### How the board is read

The listing page on authenticjobs.com is a shell: the rows are loaded by a request the page makes to the board's own listings endpoint, which answers with a small JSON envelope containing the rows as HTML. This Actor makes that same request directly, one page of 50 listings at a time, and parses the rows out of the HTML. Specifically:

- **One request per page, three seconds apart.** The site's `robots.txt` allows this path and asks for a three-second crawl delay; the Actor waits three seconds between pages. Pages are requested until the run has enough rows or the board reports it has no more.
- **No browser.** The request is a plain HTTP call with a user-agent string that names this Actor. Nothing is rendered, no JavaScript is executed.
- **No login, no proxy.** The board's listings are public and the endpoint answers a plain client, so there is nothing to sign in to and nothing to route around.
- **Newest first.** Rows are requested in the board's date order, most recent first.
- **De-duplicated.** If the board hands back the same listing twice across pages, the second copy is dropped before anything is returned or charged; `duplicatesDropped` in the run summary says how many.
- **A failed read is a failed run.** If the endpoint answers with anything other than a well-formed page of listings, the run fails with the error in the log. An empty dataset is never used to paper over a broken read.

### Honest limits

- **Job descriptions are not returned.** The description lives on each listing's own page on the board, and this Actor does not open those pages. Two reasons: the description is the employer's own writing, published on the board, and it belongs there; and fetching one page per listing at a three-second crawl delay would turn a ten-second run into a ten-minute one. Every row carries `official_url`, so the description is one click away; it just is not in the row. `description` is `null` on every row and will stay that way.
- **No salary, seniority, skills or company details.** authenticjobs.com does not publish them in its listings, so the seventeen fields in the table above are always `null`. This Actor cannot return what the board does not show.
- **`distance` does nothing.** The board has no radius search. The field is accepted and ignored, with a warning in the log.
- **`is_remote` is never `false`.** It is `true` when the listing text says "remote" and `null` when it does not. The board has no remote flag, so "not remote" is not something the Actor can know.
- **`location` is free text.** It is whatever the employer typed — a city, a country, a note about eligibility. It is not normalised into city / region / country parts.
- **`company_tagline` may be cut short.** The board itself truncates long taglines in its listing rows; the row carries what the board shows.
- **The board's search decides what matches.** A keyword is passed through to authenticjobs.com's own search. If the board's search treats a term loosely or strictly, so does this Actor.
- **The board's RSS feed is not read.** The site's `robots.txt` disallows that path, so the Actor uses the listings endpoint the page itself uses.
- **Small board, small runs.** The whole board is a couple of hundred listings at most. If you need thousands of rows per run, this is not the source that will give them to you.
- **If the board changes its markup, the Actor fails rather than guessing.** A changed response is reported as an error in the run log, not returned as rows full of `null`.

### Pricing

This Actor is priced **per listing returned**: **$0.003 per listing** (USD), one charged `listing-returned` event per data row, charged only after the row has been written to the dataset. A full read of the board, around 180 listings, therefore costs about $0.54 plus the platform's start event; a keyword search that returns 12 rows costs under four cents. The same figure is shown on the Actor's pricing tab.

What is **not** charged:

- the `NO_MATCHING_LISTINGS` row a run pushes when it finds no results;
- the `STOPPED_EARLY` row a run pushes when your spending limit cuts it short;
- duplicate listings dropped before the dataset is written.

Apify's platform also charges its own Actor start event (`apify-actor-start`) once per run: **$0.00005** at the Actor's 1 GB default memory. The platform charges that event per gigabyte, so a run started with more memory pays proportionally more for the start; this Actor needs no more than 1 GB. It is shown on the pricing tab beside the per-listing price. A typical run of this Actor is a handful of requests and a few seconds of compute.

Set a spending limit on the run if you want a hard cap: the Actor reads the result of every charge and stops cleanly with a `STOPPED_EARLY` row the moment the limit is reached, so you are never charged past it.

### Integrations

The dataset is a standard Apify dataset, so everything the platform offers applies:

- **Export** as JSON, CSV, Excel, XML or RSS from the run's Storage tab, or through the dataset API.
- **Schedules** — run it every morning and diff `source_id` against yesterday's run to see new and removed listings.
- **Webhooks** — trigger your own endpoint when a run finishes.
- **Zapier, Make, n8n** and the other Apify integrations — push new rows into Slack, Google Sheets, Airtable, Notion or a CRM.
- **API and client libraries** — start runs and read rows from JavaScript or Python.
- **MCP** — call the Actor from an AI agent through the Apify MCP server.

### Where this fits with other Actors

This Actor reads one board and stops there. It is the right choice when you want the listings on authenticjobs.com specifically — the design and creative-tech companies that post there — as a clean, keyed table you can refresh on a schedule.

It is the wrong choice if you need the job description text or salary in the row: those are not on this board's listing pages, and the row gives you `official_url` to follow instead. If you already track other job boards, the field names here are the same ones used across similar job-board datasets, so rows from this board can be appended to the same table; the `platform` column tells them apart.

### Found a problem?

If a run fails, a field looks wrong, or the board changes and the rows stop coming, please report it on the **Issues tab** of this Actor's page (https://apify.com/Pradio/authentic-jobs/issues) with the run id. The run log says which page and which request failed, and the `RUN_SUMMARY` record says what was fetched and charged, so both are worth a look first. Feature requests are welcome in the same place — but note that a field the board does not publish cannot be added by this Actor.

### Unofficial

This Actor is not affiliated with, endorsed by or connected to Authentic Jobs or authenticjobs.com. It reads publicly visible listing rows from the board's own listings endpoint, honours the site's crawl delay, and does not fetch or store the job descriptions, which remain on the board. Use the data in line with the board's terms of use and the laws that apply to you.

# Actor input Schema

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

Optional. A place name sent to authenticjobs.com's own location search and matched as text against each listing's location line, which is whatever the employer wrote (e.g. "San Francisco Bay Area", "Warsaw", "United States (Remote)"). Leave empty to search every location.

## `distance` (type: `integer`):

Ignored on this board. authenticjobs.com has no radius search, so a distance has nothing to filter by. The field is accepted so the input keeps the same shape as other job-board inputs; if you set it, the run log says it was ignored and the rows are the same as if you had left it blank.

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

Required. The search term, sent to authenticjobs.com's own search exactly as you typed it, so it behaves like typing the same words into the search box on the board. A role ("designer", "engineer"), a technology or a company name all work. What matches is decided by the board's search; results come back newest first.

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

The most listing rows one run returns. The board answers 50 listings per page; pages are read three seconds apart until the run holds this many or the board has no more, and at most this many rows are returned after duplicates are dropped. The whole board is a couple of hundred listings, so a value above that reads everything.

## Actor input object example

```json
{
  "keyword": "designer",
  "maxItems": 100
}
```

# Actor output Schema

## `rows` (type: `string`):

One row per listing found on authenticjobs.com for the keyword and location: title, company, location, job type, posting date, listing URL, logo, tagline and the board's id. A run that finds nothing holds a single NO\_MATCHING\_LISTINGS row instead.

## `summary` (type: `string`):

The run's counts: listings fetched from the board, rows written to the dataset, rows charged, duplicates dropped, and whether the spending limit stopped the run early.

# 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": "designer"
};

// Run the Actor and wait for it to finish
const run = await client.actor("pradio/authentic-jobs").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": "designer" }

# Run the Actor and wait for it to finish
run = client.actor("pradio/authentic-jobs").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": "designer"
}' |
apify call pradio/authentic-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,pradio/authentic-jobs"
        }
    }
}

```

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/RjMNEGXrQnd7SUBH2/builds/eumIBsczwGt5fsAxt/openapi.json
