{
  "title": "Remote jobs - choose employer boards and filters",
  "type": "object",
  "schemaVersion": 1,
  "properties": {
    "preset": {
      "title": "Company preset",
      "type": "string",
      "description": "'remote-first' scrapes a built-in list of 70 employer boards that were majority-remote when last verified (2026-09-25): every board had at least 5 remote postings and remote postings were at least half of its open roles. The list is in the README. The preset is used only when 'companies' and the per-platform lists are empty - listing companies replaces it. Choose 'none' when you pass your own companies and want an error rather than the preset if the list is empty.",
      "editor": "select",
      "enum": [
        "remote-first",
        "none"
      ],
      "enumTitles": [
        "remote-first (70 verified employer boards)",
        "none (only the companies I list)"
      ],
      "default": "remote-first",
      "prefill": "remote-first",
      "sectionCaption": "1 · Employer watchlist",
      "sectionDescription": "Choose a preset or employer identifiers; lists are combined according to the field help."
    },
    "companies": {
      "title": "Companies (platform auto-detected)",
      "type": "array",
      "description": "Company slugs to scrape, one per line. The slug is the path segment of the employer's board URL: job-boards.greenhouse.io/<slug>, jobs.lever.co/<slug>, jobs.ashbyhq.com/<slug>, <slug>.recruitee.com or apply.workable.com/<slug>. Each slug is tried on Greenhouse, Lever, Ashby, Recruitee and Workable in that order and the first board found is used. Slugs are case-insensitive. A slug with no board on any platform is reported and skipped. When you fill this in, the preset is not added.",
      "editor": "stringList"
    },
    "greenhouse": {
      "title": "Greenhouse boards",
      "type": "array",
      "description": "Board tokens to read from Greenhouse only, skipping detection (e.g. 'gitlab'). Remote on Greenhouse is always inferred from the location text, because the platform publishes no workplace field.",
      "editor": "stringList",
      "sectionCaption": "2 · Explicit board lists",
      "sectionDescription": "Use these optional lists when you know the employer’s ATS platform."
    },
    "lever": {
      "title": "Lever boards",
      "type": "array",
      "description": "Company slugs to read from Lever only (e.g. 'toptal'). Lever publishes a workplaceType per posting (remote / hybrid / onsite / unspecified).",
      "editor": "stringList"
    },
    "ashby": {
      "title": "Ashby boards",
      "type": "array",
      "description": "Board names to read from Ashby only (e.g. 'zapier'). Ashby publishes a workplaceType per posting (Remote / Hybrid / OnSite) but many employers leave it blank, in which case the location text decides.",
      "editor": "stringList"
    },
    "recruitee": {
      "title": "Recruitee boards",
      "type": "array",
      "description": "Subdomains to read from Recruitee only (e.g. 'timedoctor' for timedoctor.recruitee.com). Recruitee publishes remote / hybrid / on-site flags per posting.",
      "editor": "stringList"
    },
    "workable": {
      "title": "Workable accounts",
      "type": "array",
      "description": "Account names to read from Workable only (e.g. 'zyte' for apply.workable.com/zyte). Workable publishes a 'telecommuting' yes/no per posting and nothing that separates hybrid from on-site; postings without the flag are checked against their location text. Workable enforces a per-IP daily quota (HTTP 429 with a Retry-After of about 24 hours), so a board here can fail in one run and work in the next; such a board is named in SUMMARY.failedSources and the rest of the run continues. That is why no Workable board is in the preset.",
      "editor": "stringList"
    },
    "titleContains": {
      "title": "Job title contains",
      "type": "string",
      "description": "Optional. Keep only postings whose title contains this text (case-insensitive, partial match), for example 'engineer' or 'account executive'. Applied after the remote rule.",
      "editor": "textfield",
      "sectionCaption": "3 · Posting filters and detail",
      "sectionDescription": "Filter published evidence without treating missing data as a confirmed job closure."
    },
    "strictRemote": {
      "title": "Strict remote (drop hybrid/on-site even if the location text says remote)",
      "type": "boolean",
      "description": "On (default): when the platform publishes a workplace type and it is Hybrid or OnSite, the posting is dropped even if its location text says 'Remote'. Off: such a posting is kept and marked remoteBasis 'location-text'. Postings with no workplace type at all (every Greenhouse posting, and many Ashby ones) are decided by their location text either way.",
      "editor": "checkbox",
      "default": true
    },
    "onlyWithSalary": {
      "title": "Only postings with a published salary range",
      "type": "boolean",
      "description": "Keep only postings where the platform returned a structured salary (salaryMin or salaryMax). Lever, Ashby and Recruitee publish one when the employer fills it in; Greenhouse and Workable never do, so this drops every Greenhouse and Workable row. On the default preset run of 2026-09-25, 12% of the 800 rows carried a salary.",
      "editor": "checkbox",
      "default": false
    },
    "postedWithinDays": {
      "title": "Posted within the last N days",
      "type": "integer",
      "description": "Optional. Keep only postings whose postedAt is within the last N days. Rows are always sorted newest first, so this simply trims the tail. Leave empty for no date filter.",
      "editor": "number",
      "minimum": 1
    },
    "includeDescription": {
      "title": "Include full job description",
      "type": "boolean",
      "description": "Add a 'description' column with the posting text as clean plain text (HTML unescaped and stripped, list items kept as '- ' bullets, capped at 20,000 characters). Off by default because it multiplies the dataset size.",
      "editor": "checkbox",
      "default": false
    },
    "maxResultsPerCompany": {
      "title": "Maximum postings per employer",
      "type": "integer",
      "description": "Upper bound on remote postings kept from any one board, newest first. Every board is fetched in one request regardless, so this only shapes the output.",
      "editor": "number",
      "default": 100,
      "minimum": 1,
      "sectionCaption": "4 · Run limits",
      "sectionDescription": "Review both per-employer and total posting caps."
    },
    "maxResults": {
      "title": "Maximum postings across all employers",
      "type": "integer",
      "description": "Upper bound on rows in the dataset. Postings from every company are merged and sorted newest first before this cap applies, so a capped run is the freshest N remote postings across the whole list, not the first few companies. The default preset had 3,292 remote postings on 2026-09-25, so the default of 800 returns the newest 800 of them; raise it for the full list.",
      "editor": "number",
      "default": 800,
      "minimum": 1
    },
    "maxRunSeconds": {
      "title": "Run time budget (seconds)",
      "type": "integer",
      "description": "Wall-clock budget for fetching boards. When it is reached the Actor stops cleanly, keeps everything already collected, and names the companies it did not reach - instead of being killed with a failed run. The default preset finishes in well under a minute; raise this only for very long custom lists.",
      "editor": "number",
      "default": 240,
      "minimum": 30
    },
    "onlyNewSinceLastRun": {
      "title": "Only postings new since the last run",
      "type": "boolean",
      "description": "Save only matching postings not successfully saved by a previous monitor run. IDs are retained in the named remote-jobs-employer-direct-state store. The first run returns matching postings within your limits; capped or filtered-out jobs remain eligible later. A run with no new matching jobs succeeds with an empty dataset. Avoid concurrent monitor runs for the same board.",
      "editor": "checkbox",
      "default": false,
      "sectionCaption": "5 · Saved comparisons",
      "sectionDescription": "The first comparison establishes history. Reuse the same scope for later observations and avoid concurrent writers."
    }
  },
  "description": "Choose employer boards or a preset, then apply remote-work and posting filters. Use small per-employer and total caps first; missing jobs do not establish confirmed closures."
}
