# Himalayas Remote Jobs API: Salary, Seniority, Country Filters (`yadroo/himalayas-remote-jobs`) Actor

Remote job listings from the free Himalayas jobs API: title, company, employment type, seniority, country and timezone restrictions, salary normalised to a yearly figure, posting date, age in days and apply link. Filter by keyword, country, seniority, contract type, company or pay. UTC dates.

- **URL**: https://apify.com/yadroo/himalayas-remote-jobs.md
- **Developed by:** [Samat Makatov](https://apify.com/yadroo) (community)
- **Categories:** Jobs, Business, AI
- **Stats:** 2 total users, 1 monthly users, 94.1% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 job postings

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

Remote job listings from the free Himalayas jobs API: title, company, employment type, seniority, country and timezone restrictions, salary normalised to a yearly figure, posting date, age in days and apply link. Filter by keyword, country, seniority, contract type, company or pay. UTC dates.

One row per job posting, read from the public jobs API of Himalayas, a remote-work job board. Every row carries the employer, the contract type, the levels the board assigned, the countries a candidate may work from, the UTC offsets the hours cover, the pay as the employer published it **plus the same figures annualised**, the posting and expiry dates, the job families, the advert text and the link to apply. No API key, no account, no cookie, no proxy and no browser — the source answers plain JSON.

Two things this actor adds on top of the board's own search: a repeat-run mode (`onlyNew` plus `sinceHours`, so a scheduled run reports only what appeared since the last one) and comparable pay (`salaryMinAnnual` / `salaryMaxAnnual` with the `minAnnualSalary` filter the source has none for). Every filter is also re-checked on each row before it is written, because the source answers the unfiltered board when it meets a parameter it does not recognise.

> **Need it tailored to your workflow?** Missing a field, a filter, another source or a different output format? [Open an issue](https://apify.com/yadroo/himalayas-remote-jobs/issues) and describe what you need — we read every request and adapt the actor.

### Use cases

- **Fill a job board or a newsletter**: run `feed` mode with `sinceHours: 24` and `onlyNew` on a schedule and get only the postings published since yesterday, descriptions included as plain text.
- **Source candidates in one market**: a keyword plus `countries` and `seniority`, with `excludeWorldwide` so every row really is restricted to the country you report on.
- **Benchmark pay**: `onlyWithSalary` with `salaryCurrency` and `minAnnualSalary`, then read `salaryMinAnnual` / `salaryMaxAnnual` — hourly, daily, weekly, fortnightly, monthly and quarterly figures are converted to a year first, so they can be sorted and averaged.
- **Watch employers**: put company slugs into `companies` and schedule it with `onlyNew` to see every new opening of a competitor or a target employer.
- **Feed an agent or an LLM**: `descriptionFormat: "text"` gives the whole advert without markup; `"none"` keeps a 1 000-row dataset small when you only need the metadata.
- **Contract-market view for freelancers**: `employmentTypes: ["Contractor"]` with `sinceHours: 168` for everything that went up in the last week.

### Input

Nothing is required: with no input at all the actor returns the newest postings of the board. Two modes do two different jobs — `search` asks the board's own search (keyword and filters, paged), `feed` walks the whole board newest first (for mirroring and alerts).

**Strict vs forgiving.** `mode`, `seniority`, `employmentTypes`, `sort` and `descriptionFormat` are closed lists: Apify refuses a value outside the list **before the run starts**, with the allowed values in the error, so write them exactly as listed below. `countries`, `companies`, `salaryCurrency` and `parentCategories` are free text, and each is read its own way: `countries` ignores case, spacing, hyphens and accents, accepts the spellings listed in [Reference](#reference), and refuses any other value with the closest names ("did you mean …?") instead of swapping in a market you did not ask for; `companies` is lower-cased and then matched exactly, so a display name with spaces finds nothing and the run reports it in its status; `salaryCurrency` is upper-cased and refused only when it is not three letters — a code the source never uses simply returns no rows; `parentCategories` is matched case-insensitively on the rows, and a value that matched nothing is named in the run status together with the families the run did see.

| Field | Type | Default | Allowed values / notes |
|---|---|---|---|
| `mode` | string | `search` | `search` (keyword + filters) or `feed` (whole board, newest first). Strict list. |
| `query` | string | — | Free text the board searches in titles, descriptions and categories (`python`, `product designer`, `kubernetes`). Sent as typed. Ignored in `feed` mode. |
| `countries` | string\[] | — | Up to 10 country names or two-letter codes: `United States`, `us`, `Germany`, `DE`, `USA`, `UK`, `Czech Republic`. Asked for one after another, rows merged and de-duplicated; `requestedCountry` says which value found a row. See [Countries](#countries). |
| `worldwideOnly` | boolean | `false` | Keep only postings with no country restriction at all. Cannot be combined with `excludeWorldwide`. |
| `excludeWorldwide` | boolean | `false` | Drop postings with no country restriction, so a country report is not blurred by them. |
| `seniority` | string\[] | — | `Entry-level`, `Mid-level`, `Senior`, `Manager`, `Director`, `Executive`. Several = OR. Strict list. |
| `employmentTypes` | string\[] | — | `Full Time`, `Part Time`, `Contractor`, `Temporary`, `Intern`, `Volunteer`, `Other`. Several = OR. Strict list. |
| `companies` | string\[] | — | Up to 20 company slugs — the short name in a job URL (`canonical`, `gitlab`, `lemon-io`). Lower-cased, then matched exactly; a display name with spaces finds nothing and the run says so. |
| `timezoneOffset` | number | — | −12 to 14, half and quarter hours allowed (`-5`, `1`, `5.5`). Postings that list no timezone are kept, because they limit nobody. |
| `sort` | string | `recent` | `recent`, `relevant`, `salaryDesc`, `salaryAsc`. `search` mode only — `feed` is always newest first. Strict list. See [Sort orders](#sort-orders). |
| `onlyWithSalary` | boolean | `false` | Drop postings with no published figure. |
| `salaryCurrency` | string | — | Three-letter ISO 4217 code (`USD`, `EUR`, `GBP`, `PLN`). Case does not matter. No currency conversion happens, so this is how you compare pay. |
| `minAnnualSalary` | number | — | Keep postings whose `salaryMaxAnnual` is at least this much. See [Pay periods](#pay-periods). |
| `sinceHours` | number | — | 1–8760. Freshness window from the run's start, UTC. In the newest-first orders paging stops at the window. |
| `includeExpired` | boolean | `false` | Keep postings past their expiry date (`isExpired`, `expiresAt`). Off by default. |
| `onlyNew` | boolean | `false` | Remember the job URLs this run writes and, next run of the same actor or task, write only postings not written before. |
| `parentCategories` | string\[] | — | Up to 10 job families, matched on the row: `Developer`, `Operations`, `Design`, `Marketing`, `Sales`, `Data Science`… See [Job families](#job-families). A value that matched nothing is named in the run status. |
| `descriptionFormat` | string | `text` | `text` (markup stripped), `html` (as the source sends it), `none` (both columns empty). Strict list. |
| `maxItems` | number | `50` | 1–1000. Hard cap on rows, and therefore on cost. |
| `fields` | string\[] | all | Keep only these columns, **in this order**. `title`, `companyName`, `url`, `postedAt`, `fetchedAt` and `sourceMode` are always kept — the ones you do not list come first, and one you do list moves to the position you gave it. Unknown names are reported in the run status. |

### Reference

#### Countries

`countries` takes the 201 country names the board itself writes into a posting, or their ISO-3166 alpha-2 codes (`de`, `US`, `gb`). Case, spaces, hyphens, underscores and accents are ignored, so `united states`, `United-States` and `Curacao` all work. These extra spellings are accepted as well: `USA`, `U.S.`, `United States of America`; `UK`, `Great Britain`, `Britain`; `Czech Republic` (→ `Czechia`); `Russia` (→ `Russian Federation`); `Holland`, `The Netherlands`; `Korea`, `Republic of Korea` (→ `South Korea`); `Ivory Coast` (→ `Côte d'Ivoire`); `Cape Verde` (→ `Cabo Verde`); `Swaziland` (→ `Eswatini`); `Macedonia` (→ `North Macedonia`); `Syria`, `Palestine`, `Vatican`, `Bosnia`, `DR Congo` / `DRC`, `UAE` / `Emirates`, `Türkiye`, `Brasil`, `Deutschland`, `Italia`, `España`, `Polska`, `Viet Nam`, `Antigua`, `The Gambia`, `The Bahamas`, `The Philippines`, `Saint Helena`, `Republic of the Congo`.

Anything else is refused before any request is made, with the closest names in the error — `Germny` comes back as `Unknown countries "Germny" (did you mean "Germany"?)`. That is deliberate: a near miss is another market as often as a typo (Niger / Nigeria, Iraq / Iran), and a silently swapped country would hand you the wrong report.

**Worldwide postings** carry an empty `countries` list and `isWorldwide: true`. They match every country filter (anyone anywhere may apply) unless you switch `excludeWorldwide` on.

#### Seniority

| Value | Meaning |
|---|---|
| `Entry-level` | graduates and juniors |
| `Mid-level` | a few years of experience |
| `Senior` | senior individual contributors |
| `Manager` | people management |
| `Director` | heads of department |
| `Executive` | C-level and VP |

One posting can carry several levels (`["Mid-level", "Senior"]`), and a row matches when any of its levels is one you asked for.

#### Employment types

`Full Time`, `Part Time`, `Contractor` (freelance and B2B contracts), `Temporary`, `Intern`, `Volunteer`, `Other`. One value per posting, written to `employmentType` — mind the capitals and the space in `Full Time`.

#### Sort orders

| Value | Order |
|---|---|
| `recent` | newest first (the default; makes `sinceHours` and repeat runs predictable) |
| `relevant` | the board's own keyword ranking |
| `salaryDesc` | highest published pay first |
| `salaryAsc` | lowest published pay first |

The order decides which postings you see first when `maxItems` cuts the answer. The board's own ordering is not strict — a `recent` page can carry an older posting between newer ones — so paging stops at `sinceHours` only once a whole page is past the window.

#### Pay periods

The board publishes pay in the employer's own currency and period. `salaryMinAnnual` / `salaryMaxAnnual` convert the figures to a year with these factors, and nothing else:

| `salaryPeriod` | × |
|---|---|
| `hourly` | 2080 |
| `daily` | 260 |
| `weekly` | 52 |
| `fortnightly` | 26 |
| `monthly` | 12 |
| `quarterly` | 4 |
| `annual` | 1 |

No exchange rate is ever applied, so set `salaryCurrency` whenever you compare or sort by pay. A period outside the table leaves both annualised columns empty.

#### Job families

`parentCategories` is matched case-insensitively against the column of the same name. The families seen in live responses on 2026-10-07: `Content Creator`, `Customer Service`, `Data Science`, `Design`, `Developer`, `Education`, `Finance`, `Hardware Engineer`, `Healthcare`, `Human Resources`, `Legal`, `Management`, `Marketing`, `Operations`, `Product`, `Sales`. The board publishes no closed list, so this filter is applied to the rows after fetching and a value that matched nothing is named in the run status together with the families the run did see — the words in the column are always the ones that work.

### Examples

**Senior Python roles a candidate in the US may take** — tech recruiting and sourcing.

```json
{ "mode": "search", "query": "python", "countries": ["United States"], "seniority": ["Senior"], "excludeWorldwide": true, "sort": "recent", "maxItems": 15 }
```

**Worldwide postings that publish a salary, highest pay first** — compensation research.

```json
{ "mode": "search", "worldwideOnly": true, "onlyWithSalary": true, "sort": "salaryDesc", "maxItems": 15 }
```

**Remote design jobs open to candidates in Germany** — an EU hiring report; change `countries` for another market.

```json
{ "mode": "search", "query": "designer", "countries": ["Germany"], "excludeWorldwide": true, "sort": "recent", "maxItems": 10 }
```

**Contract openings from the last seven days** — freelancers and staffing agencies; set `sinceHours: 24` for a daily alert.

```json
{ "mode": "search", "employmentTypes": ["Contractor"], "sinceHours": 168, "sort": "recent", "maxItems": 15 }
```

**Everything posted on the board in the last two days, no keyword** — board operators and alert feeds; add `onlyNew` and schedule it.

```json
{ "mode": "feed", "sinceHours": 48, "descriptionFormat": "none", "onlyNew": true, "maxItems": 20 }
```

**Every current opening of one employer** — the slug is the short name in a job URL.

```json
{ "mode": "search", "companies": ["canonical"], "sort": "recent", "maxItems": 10 }
```

**Engineering roles paying at least 100 000 USD a year, four columns only** — salary benchmarking.

```json
{ "mode": "search", "query": "engineer", "onlyWithSalary": true, "salaryCurrency": "USD", "minAnnualSalary": 100000, "sort": "salaryDesc", "fields": ["title", "companyName", "salaryMaxAnnual", "salaryCurrency"], "maxItems": 10 }
```

### Output

One row per posting. A real row from a cloud run (long arrays and the advert text trimmed with `…`):

```json
{
  "title": "Software Engineer",
  "companyName": "ElitevoraSys LLC",
  "url": "https://himalayas.app/companies/elitevorasys-llc/jobs/software-engineer",
  "postedAt": "2026-09-21T03:43:48.000Z",
  "fetchedAt": "2026-10-07T20:17:31.407Z",
  "sourceMode": "search",
  "companySlug": "elitevorasys-llc",
  "companyLogoUrl": "https://cdn-images.himalayas.app/3pc0s39uob814yxkuwy550qy0jk9",
  "employmentType": "Full Time",
  "seniority": ["Senior"],
  "countries": ["United States"],
  "countryCount": 1,
  "isWorldwide": false,
  "timezoneOffsets": [-11, -10, -9.5, -9, -8, "…"],
  "requestedCountry": "United States",
  "salaryMin": 160000,
  "salaryMax": 240000,
  "salaryPeriod": "annual",
  "salaryCurrency": "USD",
  "salaryMinAnnual": 160000,
  "salaryMaxAnnual": 240000,
  "hasSalary": true,
  "expiresAt": "2026-10-17T15:00:23.000Z",
  "isExpired": false,
  "ageDays": 16.69,
  "categories": ["Software-Engineer", "Software-Developer", "…"],
  "parentCategories": ["Developer"],
  "excerpt": "About Us ElitevoraSys is a modern engineering consultancy focused on building high-performance operational platforms …",
  "descriptionText": "About Us\nElitevoraSys is a modern engineering consultancy focused on building high-performance operational platforms, automation pipelines, and data-driven workflows …",
  "descriptionHtml": null,
  "source": "himalayas.app"
}
```

| Field | Type | Meaning |
|---|---|---|
| `title` | string | Posting headline. Always filled. |
| `companyName` | string | Hiring company. Always filled. |
| `url` | string | The job page on the board — where a candidate applies, and the id rows are de-duplicated by. Always filled. |
| `postedAt` | string | null | Publication time, UTC ISO-8601. |
| `fetchedAt` | string | When this run read the posting, UTC ISO-8601. Always filled. |
| `sourceMode` | string | `search` or `feed` — which endpoint found the row. Always filled. |
| `companySlug` | string | null | Short company name from the URL — the value for `companies`. |
| `companyLogoUrl` | string | null | Address of the company logo, as a value; the actor never downloads it. |
| `employmentType` | string | null | Contract type (see [Employment types](#employment-types)). |
| `seniority` | string\[] | Levels the board assigned; can hold several. |
| `countries` | string\[] | Countries the employer can hire in. Empty = open worldwide. |
| `countryCount` | number | Size of `countries`; `0` for a worldwide posting. |
| `isWorldwide` | boolean | True when the posting names no country. |
| `timezoneOffsets` | number\[] | UTC offsets the hours cover (half and quarter hours occur). Empty = no timezone limit. |
| `requestedCountry` | string | null | Which `countries` value returned this row; null when the run asked for no country. |
| `salaryMin` / `salaryMax` | number | null | Pay exactly as published, in the period of `salaryPeriod`. |
| `salaryPeriod` | string | null | Period of the published figures, as the source words it: `hourly`, `daily`, `weekly`, `fortnightly`, `monthly`, `quarterly`, `annual` (see [Pay periods](#pay-periods)). |
| `salaryCurrency` | string | null | ISO 4217 code of the published figures. Never converted. |
| `salaryMinAnnual` / `salaryMaxAnnual` | number | null | The same pay for a year, same currency — the comparable columns, and what `minAnnualSalary` filters on. |
| `hasSalary` | boolean | True when at least one figure was published. |
| `expiresAt` | string | null | When applications close, UTC ISO-8601. |
| `isExpired` | boolean | True when the expiry date has passed; such rows need `includeExpired`. |
| `ageDays` | number | null | Days between `postedAt` and this run, two decimals. |
| `categories` | string\[] | Narrow tags the board put on the posting. |
| `parentCategories` | string\[] | Job families — what `parentCategories` matches against. |
| `excerpt` | string | null | First sentences of the advert; filled whatever `descriptionFormat` is. |
| `descriptionText` | string | null | Whole advert, markup stripped (`descriptionFormat: "text"`). |
| `descriptionHtml` | string | null | Whole advert as the source sends it (`descriptionFormat: "html"`). |
| `source` | string | Always `himalayas.app`. |

Dataset views: **Remote jobs** (job, company, contract, level, worldwide, countries, annualised pay, posted, days old, link), **Pay, as published and annualised**, **Where the job may be done, and how the row was found**.

With `fields` the row keeps only the columns you name, **in that order**; `title`, `companyName`, `url`, `postedAt`, `fetchedAt` and `sourceMode` are always kept, the ones you did not name coming first.

A run also writes a `SUMMARY` record to its key-value store: how many requests and pages it made, how many postings it read, how many rows each filter dropped, the job families it saw, whether the request budget or the freshness window ended it, and the final status message.

### Use it from code / agents

```bash
curl -X POST "https://api.apify.com/v2/acts/yadroo~himalayas-remote-jobs/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query":"python","countries":["United States"],"seniority":["Senior"],"maxItems":20}'
```

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('yadroo/himalayas-remote-jobs').call({ query: 'python', countries: ['United States'], seniority: ['Senior'], maxItems: 20 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("yadroo/himalayas-remote-jobs").call(run_input={"query": "python", "countries": ["United States"], "seniority": ["Senior"], "maxItems": 20})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

MCP: add `https://mcp.apify.com` to Claude / Cursor / any MCP client and call the `yadroo/himalayas-remote-jobs` tool with the same JSON input. For agents: `mode`, `seniority`, `employmentTypes`, `sort` and `descriptionFormat` are validated by the platform before the run starts, so an off-list value comes back as an input error with the allowed values, not as an empty dataset.

### Pricing

Pay per event: **$0.001 per run start + $0.001 per job row**. Every run is charged the start event, also a run that finds nothing. Every row written to the dataset is charged once — there are no "not found" marker rows in this actor, so you only pay for postings.

| Run | Events | Cost |
|---|---|---|
| 20 rows (one keyword, one country) | 1 start + 20 rows | $0.001 + $0.020 = **$0.021** |
| 50 rows (the default `maxItems`) | 1 start + 50 rows | $0.001 + $0.050 = **$0.051** |
| 1 000 rows (the maximum) | 1 start + 1 000 rows | $0.001 + $1.000 = **$1.001** |
| an hourly `onlyNew` alert that finds nothing new | 1 start only | **$0.001** |

Paid Apify plans get the usual tier discount on the row event (Bronze −10 %, Silver −20 %, Gold and above −30 %); the start event is flat.

Keeping it cheap: `maxItems` is a hard cap, `sinceHours` narrows a scheduled run to what is new in the window, and `onlyNew` stops you paying twice for a posting you already have. The run also stops by itself at the **Maximum cost per run** you set and keeps every row you already paid for, saying so in its status ("Stopped at your spending limit: N rows delivered").

### Limits & FAQ

- **Freshness.** The board refreshes roughly once a day, so two runs an hour apart usually see the same postings. A `sinceHours` under 24 can legitimately come back empty; that is not an error, and the status says how many postings were read.
- **Pace.** The actor makes **one request per second** to one host through a single token bucket, retries included — no numeric rate limit is published, only "429 → wait 60 seconds", which the actor honours (it also follows a `Retry-After` header). One request returns at most 20 postings, so the ceiling is about **60 requests and 1 200 postings a minute**; a run of **50 consecutive requests returned 1 000 postings in 49 seconds** (~61 requests and ~1 220 rows a minute) with no 429 and no slow-down, measured 2026-10-07, so the pace is the bucket, not the source. A 50-row run takes 5–10 seconds including start-up; a 1 000-row run about 55 seconds.
- **Request budget.** One run makes at most **60 source requests** (≈ 1 200 postings scanned). That is what stops a narrow row-side filter (a rare currency plus a high `minAnnualSalary`) from walking the whole board; when the budget ends a run, the status says so and names the rows saved. A 1 000-row run needs at least 50 requests, so it fits — but only if the filters do not discard most of each page.
- **Run timeout.** The default run timeout is 900 s and the largest allowed input finishes well inside it. A run that would still run out of time stops early by itself, keeps every row it has read and ends SUCCEEDED with "Stopped before the run timeout: N rows saved".
- **`totalCount` is not your row count.** The number the source reports is its own; it does not match what a filter really yields, and it is not written to the rows. The real end of a list is an empty page, which is where paging stops — a short page in the middle is normal here.
- **Repeated postings.** A paged search on this source returns some postings on two neighbouring pages, and two country filters can both return the same posting. Rows are de-duplicated by job URL, so you are never charged twice for the same posting inside a run.
- **Filters are verified on the row.** The source answers the unfiltered board when it meets a parameter it does not recognise, so country, seniority, contract type, company, timezone, salary, job family, freshness and expiry are all checked again on each row before it is written. The status says how many rows each filter dropped.
- **Salary coverage.** Measured on 2026-10-07: about four postings in ten publish a pay figure on the plain newest-first feed, more on a narrow keyword search (about two thirds of the senior-Python-in-the-US rows). So `onlyWithSalary` shrinks a run, by how much depends on the slice. `salaryPeriod` can be set while the figures themselves are null — `hasSalary` tells the two apart. Combine `onlyWithSalary` with `sort: "salaryDesc"` so the paid-up rows arrive first instead of being hunted page by page.
- **No currency conversion.** Pay stays in the employer's currency. `salaryCurrency` is therefore part of any pay comparison, and `minAnnualSalary` only means something together with it.
- **Expired adverts** are dropped unless `includeExpired` is on; the board keeps some of them in the feed after their expiry date.
- **Descriptions** are the employer's own HTML as the board sanitises it. `text` strips the markup, `html` keeps it, `none` leaves both columns out.
- **Empty result vs error.** An unknown country, an impossible combination of the two worldwide switches or a malformed currency code fails the run before any request, with the fix in the message. A filter that simply matched nothing is a successful run with zero rows and a status that names the filter.
- **No personal data.** These are employer postings: no candidate or recruiter names, e-mails or phone numbers are collected.

Questions or problems: open an issue on the Issues tab of this actor.

***

Made by **Yadroo**. Sibling actors: [greenhouse-jobs](https://apify.com/yadroo/greenhouse-jobs), [workable-jobs](https://apify.com/yadroo/workable-jobs), [ashby-job-postings](https://apify.com/yadroo/ashby-job-postings), [personio-jobs](https://apify.com/yadroo/personio-jobs), [recruitee-jobs](https://apify.com/yadroo/recruitee-jobs).

# Actor input Schema

## `mode` (type: `string`):

Two jobs, one per run. `search` asks the board's search for jobs matching `query` and the filters below. `feed` walks the whole board newest first and ignores `query`, `countries`, `seniority`, `employmentTypes`, `companies`, `timezoneOffset` and `sort` on the source side - this actor still applies every one of them to the rows it reads, in both modes. Use `feed` with `sinceHours` and `onlyNew` to mirror everything posted since the last run.

## `query` (type: `string`):

Free text the board searches in job titles, descriptions and categories: a language (`python`), a role (`product designer`), a skill (`kubernetes`) or a tool. Sent to the source as typed, so its own matching decides what counts as a hit. May be left empty when at least one filter below is set - `countries: ['Germany']` with `seniority: ['Senior']` is a valid run on its own. Ignored in `feed` mode.

## `countries` (type: `array`):

Country names or two-letter ISO codes: `United States`, `us`, `Germany`, `DE`. Case, spacing and common spellings are accepted (`USA`, `UK`, `Czech Republic`); an unknown value is refused with the closest names, because a wrong country returns the wrong market. A posting names the countries its employer can hire in, so this filter means "a candidate there may take the job". Several countries are asked for one after another and merged, the value that found each row going to `requestedCountry`.

## `worldwideOnly` (type: `boolean`):

Keep only postings with no country restriction at all - the ones anybody anywhere may apply for. Useful for job boards and newsletters outside the usual hiring countries. Cannot be combined with `excludeWorldwide`.

## `excludeWorldwide` (type: `boolean`):

The opposite filter: keep only postings that name countries, so every row really is restricted to the countries you asked for. Set this when you build a country report and the worldwide postings would blur it.

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

The levels the board assigns to a posting; one job can carry several of them, so a row matches when any of its levels is one you asked for. Several values are combined with OR. The platform refuses any value outside the six above before the run starts, so write them exactly as listed.

## `employmentTypes` (type: `array`):

Contract type as the board records it - one value per posting, written to the `employmentType` column. Several values are combined with OR; leave empty for every type. The platform refuses any other value before the run starts, so the spelling with the capital letters and the space (`Full Time`) is the one to send.

## `companies` (type: `array`):

Watch named employers instead of the whole market: `canonical`, `gitlab`, `automattic`. A slug is the short name in the job URL (`.../companies/canonical/jobs/...`) and it is written to every row as `companySlug`, so one run tells you the slugs to reuse. Sent to the source as typed and matched exactly - a company name with spaces or capitals (`Canonical Ltd`) is not a slug and finds nothing, which the run reports in its status message.

## `timezoneOffset` (type: `number`):

Keep jobs whose working hours cover this UTC offset: `-5` for US east coast, `1` for central Europe, `5.5` for India, `6` for Kazakhstan. Postings that list no timezone restriction are kept as well, because they put no limit on you. Half-hour and quarter-hour offsets are allowed, as the source uses them (`-9.5`, `5.75`). The offsets a posting allows are written to `timezoneOffsets`.

## `sort` (type: `string`):

Which jobs you see first when `maxItems` cuts the answer. Newest first is the default, because it makes repeat runs and `sinceHours` predictable; `relevant` is the source's own keyword ranking; the salary orders put the postings that publish a figure in front, which is the fastest way to fill a run with paid-up roles. Only used in `search` mode - `feed` is always newest first.

## `onlyWithSalary` (type: `boolean`):

Drop postings with no figure. About four postings in ten publish pay on the newest-first feed and more on a narrow search, so this filter shrinks a run - combine it with `sort: 'salaryDesc'` so the paid-up rows arrive first instead of being hunted page by page.

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

Three-letter ISO 4217 code of the currency the figure must be in: `USD`, `EUR`, `GBP`, `PLN`. Case does not matter. Needed whenever you compare pay, because the board publishes salaries in the employer's own currency and this actor applies no exchange rates. A code the source never uses simply returns no rows, and the run says so.

## `minAnnualSalary` (type: `number`):

Keep postings whose upper salary figure, converted to a yearly amount, is at least this much - `salaryMaxAnnual` in the row. Pay in a shorter or longer period is annualised first (hourly x 2080, daily x 260, weekly x 52, fortnightly x 26, monthly x 12, quarterly x 4), which is what makes a pay filter possible at all on a board that mixes the periods. No currency conversion happens, so set `salaryCurrency` too or you are comparing euros with forints.

## `sinceHours` (type: `number`):

Freshness window counted from the run's start in UTC: `24` for the last day, `168` for the last week. Paging stops once the source hands out postings older than the window in the newest-first orders (`recent` and `feed` mode), so a scheduled hourly run stays cheap. In the other orders it filters the rows instead. Note the board refreshes about once a day, so a window under 24 hours can legitimately come back empty.

## `includeExpired` (type: `boolean`):

The source publishes an expiry date with every posting and keeps some of them in the feed after that date. They are dropped by default, because an advert nobody can apply for is noise in a job board or an alert. Switch on for market history, and tell the rows apart by `isExpired` and `expiresAt`.

## `onlyNew` (type: `boolean`):

Remember the job URLs this run writes in the actor's key-value store and, on the next run of the same actor or task, write only postings that were not written before. Made for scheduled watching - a keyword, a country or a company - so each run holds just what appeared since. The memory is kept per actor or task, so two tasks watching two keywords do not interfere.

## `parentCategories` (type: `array`):

Broad families the board groups roles into, matched case-insensitively against the `parentCategories` column of each row: `Developer`, `Operations`, `Design`, `Marketing`, `Sales`, `Customer Support`, `Finance`, `Data`. The source has no filter for them, so the actor applies this one to the rows after fetching - it narrows what you pay for, not how much is fetched. A value that matched nothing is named in the run's status message, so you can fix it with the words you see in the column.

## `descriptionFormat` (type: `string`):

The full advert is several kilobytes of HTML. Plain text is the default because that is what models, search indexes and spreadsheets can use; `html` keeps the markup for a site that renders it; `none` leaves both columns empty and keeps a 1000-row dataset small. The short `excerpt` is always filled either way.

## `maxItems` (type: `number`):

Hard cap on the postings this run writes, and therefore on what it costs. Paging stops as soon as the cap is reached. The source hands out 20 jobs per request, so 1000 rows means at least 50 requests at a polite pace - keep scheduled runs small and let `onlyNew` or `sinceHours` do the narrowing.

## `fields` (type: `array`):

Keep only the named columns, in the order given - for example `title, companyName, salaryMaxAnnual, url`. Empty = every column. `title`, `companyName`, `url`, `postedAt`, `fetchedAt` and `sourceMode` are always present; name one of them here and it moves to the position you gave it. Unknown names are reported in the status message instead of being dropped in silence.

## Actor input object example

```json
{
  "mode": "search",
  "query": "python",
  "countries": [
    "United States"
  ],
  "worldwideOnly": false,
  "excludeWorldwide": false,
  "seniority": [
    "Senior"
  ],
  "sort": "recent",
  "onlyWithSalary": false,
  "includeExpired": false,
  "onlyNew": false,
  "descriptionFormat": "text",
  "maxItems": 50
}
```

# Actor output Schema

## `results` (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 = {
    "query": "python",
    "countries": [
        "United States"
    ],
    "seniority": [
        "Senior"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("yadroo/himalayas-remote-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 = {
    "query": "python",
    "countries": ["United States"],
    "seniority": ["Senior"],
}

# Run the Actor and wait for it to finish
run = client.actor("yadroo/himalayas-remote-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 '{
  "query": "python",
  "countries": [
    "United States"
  ],
  "seniority": [
    "Senior"
  ]
}' |
apify call yadroo/himalayas-remote-jobs --silent --output-dataset

```

## MCP server setup

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