{
  "openapi": "3.0.1",
  "info": {
    "title": "Remote Jobs Scraper – Jobicy, Working Nomads, Arbeitnow +4",
    "description": "Remote job postings from seven public boards (Remotive, Remote OK, Jobicy, Arbeitnow, Working Nomads, Himalayas, We Work Remotely) in one normalized, newest-first schema, de-duplicated so the same job is never billed twice. Filter by keyword, company, location, salary and date. Covers Arbeitnow API.",
    "version": "0.1",
    "x-build-id": "zOMrdDoySsR2dCnJj"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/fetchsmith~remote-jobs-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-fetchsmith-remote-jobs-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/fetchsmith~remote-jobs-scraper/runs": {
      "post": {
        "operationId": "runs-sync-fetchsmith-remote-jobs-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/fetchsmith~remote-jobs-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-fetchsmith-remote-jobs-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "properties": {
          "sources": {
            "title": "Job boards",
            "type": "array",
            "description": "Which public job-board APIs to pull from. Any subset of: \"remotive\" (Remotive, remote tech/design/marketing roles, worldwide), \"remoteok\" (Remote OK, ~100 most recent postings, USD salary ranges on many rows), \"jobicy\" (Jobicy, 50 most recent, salary range + currency + period on many rows), \"arbeitnow\" (Arbeitnow, a general European board — only its rows flagged remote are returned), \"workingnomads\" (Working Nomads, general remote board with a category field on every row), \"himalayas\" (Himalayas, the largest board here at ~100k live postings, cursor-paginated 20 at a time — seniority and category tags on most rows, numeric salary range on about a quarter of them), \"wwr\" (We Work Remotely, a long-running board with ~89 live postings, job type and category fields on every row, no salary data). Leave empty for all seven. An unknown name fails the run rather than being silently ignored.",
            "items": {
              "type": "string",
              "enum": [
                "remotive",
                "remoteok",
                "jobicy",
                "arbeitnow",
                "workingnomads",
                "himalayas",
                "wwr"
              ]
            },
            "default": [
              "remotive",
              "remoteok",
              "jobicy",
              "arbeitnow",
              "workingnomads",
              "himalayas",
              "wwr"
            ]
          },
          "searchKeyword": {
            "title": "Keyword",
            "type": "string",
            "description": "Only keep postings whose title, company, category or tags contain this text (case-insensitive), e.g. \"python\" or \"customer support\". It is also passed to Jobicy's own search parameter, so that board filters server-side as well. Remotive's API ignores every parameter it documents (re-verified 2026-09-28), so its rows are narrowed by this Actor alone. Leave empty for all postings."
          },
          "titleExcludeKeyword": {
            "title": "Title exclude keyword",
            "type": "string",
            "description": "Drop postings whose title contains this text (case-insensitive), e.g. \"Senior\" to exclude senior roles. Leave empty for no exclusion."
          },
          "companyKeyword": {
            "title": "Company filter",
            "type": "string",
            "description": "Only keep postings whose company name contains this text (case-insensitive). Leave empty for all companies."
          },
          "locationKeyword": {
            "title": "Location / region filter",
            "type": "string",
            "description": "Only keep postings whose location field contains this text (case-insensitive), e.g. \"USA\", \"Europe\", \"Germany\". Note that remote boards store a hiring REGION (\"USA, Canada\", \"Worldwide\", \"UK\") rather than a city, and some rows have no location at all — those are dropped when this filter is set. Leave empty for all locations."
          },
          "jobTypeKeyword": {
            "title": "Job-type filter",
            "type": "string",
            "description": "Only keep postings whose job-type field contains this text (case-insensitive), e.g. \"full\" or \"contract\". This field is published by Remotive, Jobicy, Arbeitnow, Himalayas and We Work Remotely (wwr); the `remoteok` and `workingnomads` boards carry no job-type field in their API at all, so their rows never match a non-empty filter here (dropped, not guessed at). Leave empty for all job types."
          },
          "seniorityKeyword": {
            "title": "Seniority filter",
            "type": "string",
            "description": "Only keep postings whose seniority-level field contains this text (case-insensitive), e.g. \"senior\" or \"entry\". Two boards publish a clean seniority field distinct from job type: Jobicy (\"Any\", \"Entry-Level, Junior\", \"Senior\", \"Director\") and Himalayas (\"Entry-level\", \"Mid-level\", \"Senior\", \"Manager\", \"Director\", \"Executive\"). The other five boards carry no such field, so their rows never match a non-empty filter here (dropped, not guessed at from the job title). Leave empty for all seniority levels."
          },
          "postedAfter": {
            "title": "Posted on or after",
            "type": "string",
            "description": "Only keep postings published on or after this date, in YYYY-MM-DD (UTC). The whole day is included. A value that is not a real YYYY-MM-DD calendar date FAILS the run with an explanatory error — it is never ignored, because ignoring it would return and charge for jobs outside your window. Rows with no publication date are dropped when this filter is set."
          },
          "postedBefore": {
            "title": "Posted on or before",
            "type": "string",
            "description": "Only keep postings published on or before this date, in YYYY-MM-DD (UTC). The whole day is included (ends 23:59:59.999Z). Same strict parsing as \"Posted on or after\": a typo fails the run instead of silently widening the result set. WATCH MODE WARNING: combined with watchLabel, a FIXED value here is a permanent dead end once the real date passes it — a newly-published posting's own publishedAt is always \"now\", so no new posting can ever be dated on or before an already-passed cutoff again. Leave it unset in a watched query; postedAfter alone is safe to keep. See README FAQ."
          },
          "salaryOnly": {
            "title": "Only postings with salary info",
            "type": "boolean",
            "description": "Keep only postings that carry a salary range or salary text. Remote OK and Jobicy supply numeric ranges on many rows; Remotive supplies a free-text range; Arbeitnow, Working Nomads and We Work Remotely supply none, so enabling this drops all their rows.",
            "default": false
          },
          "minSalaryAnnual": {
            "title": "Minimum salary (annualized, USD)",
            "minimum": 1,
            "type": "integer",
            "description": "Only keep postings whose stated salary floor (salaryMin), annualized, is at least this many USD per year, e.g. 100000. Annualization assumes 2080 paid hours/year (hourly) or 260 paid days/year (daily); monthly x12 and weekly x52. Dropped (not assumed to pass) when any of the following is true, since comparing the floor would otherwise be a guess: the posting has no stated period (we never infer a period from the number's size), the posting states only a ceiling with no floor (e.g. \"Up to $90k\"), or the posting's currency is not explicitly USD (no currency conversion is performed, so a non-USD or unstated-currency number cannot be honestly compared to a USD floor). Leave empty for no floor."
          },
          "dedupe": {
            "title": "De-duplicate across boards",
            "type": "boolean",
            "description": "Fold postings that appear on more than one board into a single row (matched on normalized company + job title, newest kept). The row then lists the other boards in \"alsoOn\" and their links in \"duplicateUrls\". De-duplication happens BEFORE charging, so you are never billed twice for one job. Turn off to get one row per board copy.",
            "default": true
          },
          "resultOrder": {
            "title": "Result order",
            "enum": [
              "balanced",
              "newest"
            ],
            "type": "string",
            "description": "How the \"Max results\" cap picks which postings to deliver. \"Balanced across boards\" takes one posting per board per round, newest-first inside each board, so every board you selected is represented within the first few rows — the first row is still the newest posting overall. \"Global newest-first\" sorts all boards together by date, which in practice fills a small cap from a single board, because the boards do not publish comparable timestamps (Arbeitnow stamps its whole feed with the current day's crawl time, so measured on 2026-10-18 the top 100 of a 7-board merge were 100% Arbeitnow). Does not change how many rows you are charged for.",
            "default": "balanced"
          },
          "includeDescription": {
            "title": "Include job description HTML",
            "type": "boolean",
            "description": "Add the board's full job description HTML as \"descriptionHtml\". Off by default because descriptions are large and most pipelines only need the structured fields plus the link.",
            "default": false
          },
          "maxPagesPerSource": {
            "title": "Max pages per source",
            "minimum": 1,
            "maximum": 20,
            "type": "integer",
            "description": "Only affects the paginated sources — Arbeitnow (the board chooses the page size, not you: 326 / 325 / 100 postings on pages 1-3 measured 2026-09-28, and only ~20 / 12 / 1 of those are flagged remote) and Himalayas (20 postings per page, cursor-based). The other five boards return their whole feed in one request. In watch mode this value is part of the label fingerprint: it decides how deep a baseline reaches, so changing it starts a fresh (free) baseline rather than reporting older postings from the newly-reached pages as new.",
            "default": 2
          },
          "maxResults": {
            "title": "Max results",
            "minimum": 1,
            "maximum": 5000,
            "type": "integer",
            "description": "Stop after this many unique postings have been pushed (and charged).",
            "default": 100
          },
          "watchLabel": {
            "title": "Watch label (only new postings since last run)",
            "type": "string",
            "description": "Optional. Name a saved job alert (e.g. \"backend-remote-eu\") and this run returns ONLY postings that were not delivered under that same label and filter set before, instead of the whole open-roles list every time. The first run for a label is a free baseline: it records which postings are already open (up to 5000) and returns zero rows, charging nothing. Run it again later — on a schedule, typically — to get only the roles that opened since then. The baseline lives in your own Apify account (a named key-value store, \"fetchsmith-remote-jobs-watch\"), keyed by the label plus a fingerprint of the sources, every match filter and \"maxPagesPerSource\", so changing any of those starts a fresh baseline instead of dumping previously-excluded postings as \"new\"."
          },
          "watchEvents": {
            "title": "Watch events to report",
            "type": "array",
            "description": "Optional. Only report these kinds of change in watch mode — e.g. pick \"Salary appeared\" alone to be alerted only when an already-delivered posting later gains a salary, and never pay for anything else. Leave empty for both. The change event compares against the salary state recorded the last time the posting was delivered, so it only starts firing on the second run after a baseline created before this feature existed. Ignored when \"Watch label\" is empty.",
            "items": {
              "type": "string",
              "enum": [
                "new",
                "salaryAdded"
              ],
              "enumTitles": [
                "New posting",
                "Salary appeared (posting already delivered, now shows a salary)"
              ]
            },
            "default": []
          },
          "webhookUrl": {
            "title": "Webhook URL (notify on completion)",
            "type": "string",
            "description": "Optional. An http(s) URL to POST a small JSON summary to when the run finishes — postings pushed, and watch-label new/skipped counts if Watch label is set. A convenience for callers who want a completion ping without setting up an Apify platform webhook. Best-effort: a failed or slow webhook is logged as a warning and never fails the run or affects charging. Leave empty to skip."
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}