{
  "data": {
    "id": "LcvqVId2MMBCvIVPg",
    "actId": "1dwlZdSypmIQ5vq5X",
    "userId": "9Um4NEFR7z42j35AD",
    "startedAt": "2026-09-19T16:09:49.330Z",
    "finishedAt": "2026-09-19T16:09:58.663Z",
    "status": "SUCCEEDED",
    "meta": {
      "origin": "CLI"
    },
    "stats": {
      "durationMillis": 9265,
      "runTimeSecs": 9.265,
      "computeUnits": 0.010294444444444445,
      "imageSizeBytes": 594020864
    },
    "options": {
      "useCache": true,
      "betaPackages": false,
      "memoryMbytes": 4096,
      "diskMbytes": 8192
    },
    "exitCode": 0,
    "inputSchema": "{\n  \"title\": \"Google Jobs search\",\n  \"type\": \"object\",\n  \"schemaVersion\": 1,\n  \"properties\": {\n    \"market\": {\n      \"title\": \"1. Google market\",\n      \"type\": \"string\",\n      \"description\": \"Select a tested Google country site and result language. This setting does not guarantee that every job is in this country.\",\n      \"editor\": \"select\",\n      \"sectionCaption\": \"Choose jobs\",\n      \"sectionDescription\": \"First select the Google market and language. Then enter job titles or keywords. The optional location applies to every search. Maximum saved jobs is one shared result and billing limit for the run.\",\n      \"default\": \"US-en\",\n      \"enum\": [\n        \"DZ-fr\",\n        \"AR-es\",\n        \"AT-de\",\n        \"BS-en\",\n        \"BH-ar\",\n        \"BD-en\",\n        \"BY-ru\",\n        \"BE-nl\",\n        \"BE-fr\",\n        \"BJ-fr\",\n        \"BO-es\",\n        \"BW-en\",\n        \"BR-pt\",\n        \"BF-fr\",\n        \"CM-fr\",\n        \"CA-en\",\n        \"CA-fr\",\n        \"CL-es\",\n        \"CO-es\",\n        \"CR-es\",\n        \"CI-fr\",\n        \"CD-fr\",\n        \"DK-da\",\n        \"DO-es\",\n        \"EC-es\",\n        \"EG-ar\",\n        \"SV-es\",\n        \"ET-en\",\n        \"FR-fr\",\n        \"DE-de\",\n        \"GH-en\",\n        \"GR-el\",\n        \"GT-es\",\n        \"HT-fr\",\n        \"HN-es\",\n        \"HK-en\",\n        \"IN-en\",\n        \"ID-id\",\n        \"IT-it\",\n        \"JM-en\",\n        \"JP-ja\",\n        \"JO-ar\",\n        \"KZ-ru\",\n        \"KE-en\",\n        \"KW-ar\",\n        \"KG-ru\",\n        \"LB-ar\",\n        \"LS-en\",\n        \"MW-en\",\n        \"MY-en\",\n        \"ML-fr\",\n        \"MU-en\",\n        \"MX-es\",\n        \"MA-fr\",\n        \"NA-en\",\n        \"NL-nl\",\n        \"NG-en\",\n        \"OM-ar\",\n        \"PK-en\",\n        \"PA-es\",\n        \"PY-es\",\n        \"PE-es\",\n        \"PH-en\",\n        \"PT-pt\",\n        \"PR-es\",\n        \"QA-ar\",\n        \"RU-ru\",\n        \"RW-en\",\n        \"SA-ar\",\n        \"SN-fr\",\n        \"SC-en\",\n        \"SG-en\",\n        \"SO-en\",\n        \"ZA-en\",\n        \"ES-es\",\n        \"LK-en\",\n        \"CH-fr\",\n        \"CH-de\",\n        \"CH-it\",\n        \"TW-zh-TW\",\n        \"TZ-en\",\n        \"TH-th\",\n        \"TG-fr\",\n        \"TT-en\",\n        \"TN-fr\",\n        \"UG-en\",\n        \"AE-en\",\n        \"GB-en\",\n        \"US-en\",\n        \"UY-es\",\n        \"UZ-ru\",\n        \"VE-es\",\n        \"VN-vi\",\n        \"ZM-en\",\n        \"ZW-en\"\n      ],\n      \"enumTitles\": [\n        \"Algeria - French\",\n        \"Argentina - Spanish\",\n        \"Austria - German\",\n        \"Bahamas - English\",\n        \"Bahrain - Arabic\",\n        \"Bangladesh - English\",\n        \"Belarus - Russian\",\n        \"Belgium - Dutch\",\n        \"Belgium - French\",\n        \"Benin - French\",\n        \"Bolivia - Spanish\",\n        \"Botswana - English\",\n        \"Brazil - Portuguese\",\n        \"Burkina Faso - French\",\n        \"Cameroon - French\",\n        \"Canada - English\",\n        \"Canada - French\",\n        \"Chile - Spanish\",\n        \"Colombia - Spanish\",\n        \"Costa Rica - Spanish\",\n        \"Côte d’Ivoire - French\",\n        \"Democratic Republic of the Congo - French\",\n        \"Denmark - Danish\",\n        \"Dominican Republic - Spanish\",\n        \"Ecuador - Spanish\",\n        \"Egypt - Arabic\",\n        \"El Salvador - Spanish\",\n        \"Ethiopia - English\",\n        \"France - French\",\n        \"Germany - German\",\n        \"Ghana - English\",\n        \"Greece - Greek\",\n        \"Guatemala - Spanish\",\n        \"Haiti - French\",\n        \"Honduras - Spanish\",\n        \"Hong Kong - English\",\n        \"India - English\",\n        \"Indonesia - Indonesian\",\n        \"Italy - Italian\",\n        \"Jamaica - English\",\n        \"Japan - Japanese\",\n        \"Jordan - Arabic\",\n        \"Kazakhstan - Russian\",\n        \"Kenya - English\",\n        \"Kuwait - Arabic\",\n        \"Kyrgyzstan - Russian\",\n        \"Lebanon - Arabic\",\n        \"Lesotho - English\",\n        \"Malawi - English\",\n        \"Malaysia - English\",\n        \"Mali - French\",\n        \"Mauritius - English\",\n        \"Mexico - Spanish\",\n        \"Morocco - French\",\n        \"Namibia - English\",\n        \"Netherlands - Dutch\",\n        \"Nigeria - English\",\n        \"Oman - Arabic\",\n        \"Pakistan - English\",\n        \"Panama - Spanish\",\n        \"Paraguay - Spanish\",\n        \"Peru - Spanish\",\n        \"Philippines - English\",\n        \"Portugal - Portuguese\",\n        \"Puerto Rico - Spanish\",\n        \"Qatar - Arabic\",\n        \"Russia - Russian\",\n        \"Rwanda - English\",\n        \"Saudi Arabia - Arabic\",\n        \"Senegal - French\",\n        \"Seychelles - English\",\n        \"Singapore - English\",\n        \"Somalia - English\",\n        \"South Africa - English\",\n        \"Spain - Spanish\",\n        \"Sri Lanka - English\",\n        \"Switzerland - French\",\n        \"Switzerland - German\",\n        \"Switzerland - Italian\",\n        \"Taiwan - Traditional Chinese\",\n        \"Tanzania - English\",\n        \"Thailand - Thai\",\n        \"Togo - French\",\n        \"Trinidad and Tobago - English\",\n        \"Tunisia - French\",\n        \"Uganda - English\",\n        \"United Arab Emirates - English\",\n        \"United Kingdom - English\",\n        \"United States - English\",\n        \"Uruguay - Spanish\",\n        \"Uzbekistan - Russian\",\n        \"Venezuela - Spanish\",\n        \"Vietnam - Vietnamese\",\n        \"Zambia - English\",\n        \"Zimbabwe - English\"\n      ]\n    },\n    \"queries\": {\n      \"title\": \"2. Job titles or keywords\",\n      \"type\": \"array\",\n      \"description\": \"Enter one job title, skill, employer, or phrase per line. Do not add the location here when you use the location field below.\",\n      \"editor\": \"stringList\",\n      \"default\": [\n        \"data analyst\"\n      ],\n      \"prefill\": [\n        \"data analyst\"\n      ],\n      \"minItems\": 1,\n      \"maxItems\": 100,\n      \"items\": {\n        \"type\": \"string\",\n        \"minLength\": 1,\n        \"pattern\": \"\\\\S\",\n        \"maxLength\": 200\n      }\n    },\n    \"location\": {\n      \"title\": \"3. City or region - optional\",\n      \"type\": \"string\",\n      \"description\": \"Google adds this location to every search. Leave it blank for broad results in the selected market. Use one location per run.\",\n      \"editor\": \"textfield\",\n      \"default\": \"\",\n      \"maxLength\": 200\n    },\n    \"maxItems\": {\n      \"title\": \"Maximum saved jobs (shared limit)\",\n      \"type\": \"integer\",\n      \"description\": \"The Actor stops after it saves this many unique jobs across all searches. You pay only for saved jobs. Google can return fewer jobs.\",\n      \"editor\": \"number\",\n      \"default\": 25,\n      \"minimum\": 1,\n      \"maximum\": 1000\n    }\n  },\n  \"required\": [\n    \"queries\"\n  ]\n}",
    "readme": "# Google Jobs Scraper - Job Search & Apply Links\n\n## 🔎 What does Google Jobs Scraper do?\n\nGoogle Jobs Scraper searches public Google Jobs results by keyword and\nlocation. It saves one clean row for each unique job.\n\nSelect a Google market, add one or more searches, choose an optional shared\nlocation, and set a saved-row limit. Each row can include the job title,\ncompany, location, full description, salary text, schedule, posting age,\nGoogle job URL, and direct application options.\n\nUse it to:\n\n- 🔔 Build scheduled job alerts.\n- 🧑‍💼 Monitor hiring by company, role, or location.\n- 📊 Study public job demand and salary text.\n- 🧱 Add job listings to a database, dashboard, or job board.\n- 🚀 Find direct employer, career-site, and job-board application links.\n- 📦 Export data as JSON, JSONL, CSV, Excel, XML, RSS, or HTML.\n\nThe Actor reads public, logged-out Google Jobs results. It does not need a\nGoogle account, browser cookies, or an external API key.\n\n## 📦 What Google job data can I extract?\n\nEach default-dataset row represents one unique Google job found during one\nActor run.\n\n| Data group           | Example fields                                           |\n| -------------------- | -------------------------------------------------------- |\n| 🔍 Search context    | `query`, `requestedLocation`, `country`, `language`      |\n| 🆔 Job identity      | `jobId`, `jobUrl`                                        |\n| 💼 Job details       | `title`, `companyName`, `location`, `via`, `description` |\n| 💰 Job facts         | `postedAtRaw`, `scheduleType`, `salaryRaw`, `tags`       |\n| 🚀 Application links | `applyOptions[].title`, `applyOptions[].link`            |\n| 🚦 Row state         | `status`, `statusReason`                                 |\n| 🕐 Run context       | `observedAt`, `runId`, `schemaVersion`                   |\n\nMissing public values stay `null` or use an empty list. The Actor does not\nguess missing values or replace them with zero.\n\n### 🚀 Direct application links\n\nThe `applyOptions` list contains the application pages that Google supplies.\nEach item has a provider `title` and a direct `link`. The list can include an\nemployer applicant tracking system (ATS), a company career page, or a job\nboard. An ATS is software that an employer uses to receive job applications.\n\nOne job can have several application options. The raw dataset row contains the\nfull list. The default Results table does not show this nested field.\n\n## ⚙️ Can I use this Actor through an API?\n\nYes. Run the Actor in Apify Console or connect it to the Apify API, Python SDK,\nJavaScript SDK, webhooks, schedules, or Apify integrations.\n\nKeep your Apify token in an environment variable or secret store. Do not place\nit in source code.\n\n```bash\ncurl -X POST \\\n  \"https://api.apify.com/v2/acts/chronometrica~google-jobs-scraper/runs?token=$APIFY_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"market\": \"CA-en\",\n    \"queries\": [\"data analyst\", \"business intelligence analyst\"],\n    \"location\": \"Toronto, Ontario, Canada\",\n    \"maxItems\": 25\n  }'\n```\n\nUse the returned run ID to check the run, read the default dataset, or attach a\nwebhook. The same JSON input works with the Apify Python and JavaScript clients.\n\n## 🎯 Why scrape Google Jobs?\n\nGoogle Jobs brings listings from employers, career sites, and job boards into\none search view. This can give you broader public job coverage than one job\nboard.\n\n| Use case                 | How the data helps                                          |\n| ------------------------ | ----------------------------------------------------------- |\n| 🔔 Job alerts            | Find new jobs for a role and location on a schedule.        |\n| 🧑‍💼 Recruiting research   | Track companies, roles, locations, and hiring language.     |\n| 📊 Labor-market analysis | Compare visible demand, salary text, and posting activity.  |\n| 🧱 Job-board feeds       | Load unique job rows and direct application choices.        |\n| 🕵️ Employer monitoring   | Watch hiring for selected companies or role groups.         |\n| 🤖 Automated workflows   | Send jobs to a database, spreadsheet, CRM, or alert system. |\n\nThe Actor collects source data. It does not rank candidates, assess people, or\nmake hiring decisions.\n\n## 💵 Pricing\n\nGoogle Jobs Scraper uses pay-per-result pricing. One result means one unique\njob row saved to the default dataset. The price depends on your Apify plan.\n\n| Apify plan | Price per 1,000 saved jobs |\n| ---------- | -------------------------- |\n| Free       | $5.00                      |\n| Starter    | $4.50                      |\n| Scale      | $4.00                      |\n| Business   | $3.50                      |\n\nDuplicate and invalid candidates do not create a result charge. Use `maxItems`\nto set a firm saved-row limit. Check the Store **Pricing** tab for the current\nrate before a production run.\n\nStart with `maxItems: 10` when you test a new search.\n\n## 🚀 How do I use Google Jobs Scraper?\n\n1. Open **Google Jobs Scraper** in Apify Console.\n2. Select the Google market and result language.\n3. Enter one or more job titles or keywords.\n4. Enter one optional city or region for all searches.\n5. Set the maximum number of jobs to save.\n6. Click **Start**.\n7. Open **Output** to inspect the dataset and run summary.\n8. Download the data or connect the run to another tool.\n\n## ⬇️ Input\n\n```json\n{\n  \"market\": \"CA-en\",\n  \"queries\": [\"data analyst\", \"business intelligence analyst\"],\n  \"location\": \"Toronto, Ontario, Canada\",\n  \"maxItems\": 25\n}\n```\n\n| Setting    | What it controls                                                                       |\n| ---------- | -------------------------------------------------------------------------------------- |\n| `market`   | Tested Google country site and result language. It is not a strict job-country filter. |\n| `queries`  | One to 100 job-title or keyword searches. Each query can have up to 200 characters.    |\n| `location` | One optional city, region, or country for all searches. A city gives precise results.  |\n| `maxItems` | Maximum unique rows saved across the run. The allowed range is 1 to 1,000.             |\n\nThe form offers 95 tested country and language pairs. Countries with more than\none tested language have one option for each language. The Actor still accepts\nthe earlier `country` and `language` API fields when they form one supported\nmarket pair.\n\nThe Actor removes duplicate queries without treating letter case as different.\nIt gives active searches balanced row turns in query order. It requests only\nthe search pages that can still contribute to the shared saved-row limit. This\nprevents one broad search from using the full limit and avoids paying for pages\nthat cannot add a saved row.\n\nThe Actor follows Google's continuation tokens until it reaches `maxItems`,\nthe source ends, or the 100-page per-query safety cap.\n\n## ⬆️ Output sample\n\nResults are stored in the default dataset. Field order is stable for JSON,\nCSV, spreadsheet, API, and database work.\n\n```json\n{\n  \"query\": \"data analyst\",\n  \"requestedLocation\": \"Toronto, Ontario, Canada\",\n  \"country\": \"CA\",\n  \"language\": \"en\",\n  \"jobId\": \"example-job-id\",\n  \"jobUrl\": \"https://www.google.com/search?ibp=htl;jobs&htidocid=example-job-id\",\n  \"title\": \"Data Analyst\",\n  \"companyName\": \"Example Company\",\n  \"location\": \"Toronto, ON\",\n  \"via\": \"Example Careers\",\n  \"description\": \"Analyze business data and prepare reports.\",\n  \"postedAtRaw\": \"Posted 2 days ago\",\n  \"scheduleType\": \"Full-time\",\n  \"salaryRaw\": \"$70,000-$85,000 a year\",\n  \"tags\": [\"2 days ago\", \"Full-time\", \"No degree mentioned\"],\n  \"applyOptions\": [\n    {\n      \"title\": \"Apply on Example Careers\",\n      \"link\": \"https://jobs.example.com/data-analyst\"\n    }\n  ],\n  \"observedAt\": \"2026-08-08T23:40:50.000Z\",\n  \"runId\": \"example-run-id\",\n  \"schemaVersion\": \"4.0\",\n  \"status\": \"succeeded\",\n  \"statusReason\": \"Job listing returned by Google Jobs.\"\n}\n```\n\n### 📌 Run summary\n\nThe `OUTPUT` record is stored in the default key-value store. It reports:\n\n- `queriesReceived`: number of unique searches received.\n- `sourceStates`: result state for each search.\n- `pagesRequested`: source batches requested.\n- `candidatesSeen`: jobs found before validation and duplicate removal.\n- `rowsSaved`: unique job rows saved and charged.\n- `duplicatesSkipped`: repeated job IDs not saved or charged.\n- `invalidCandidatesSkipped`: candidates without the required identity fields.\n- `stopReason`: `saved_row_cap_reached` or `sources_exhausted`.\n- `startedAt` and `finishedAt`: run times in ISO format.\n\nUse this record to tell the difference between a small result set, duplicate\njobs, a source error, and a completed saved-row limit.\n\nA `partial` source state means that Google returned useful jobs before a later\ncontinuation request failed. The Actor keeps and charges only those useful\nrows.\n\n## 🚦 Status, identity, and data quality\n\n- Every saved row has a valid `jobId`, `title`, and `companyName`.\n- Every saved row has `status: \"succeeded\"`.\n- The Actor uses `jobId` to remove duplicates across all queries in one run.\n- `observedAt` is the collection time. `postedAtRaw` is source text, not a\n  normalized posting time.\n- `tags` contains the exact job tags that Google supplies. The Actor does not\n  infer or classify these values.\n- `description`, salary, schedule, location, source, job URL, and application\n  options can be missing.\n- If completed searches contain no jobs, the Actor succeeds with\n  `status: \"no_results\"` in `OUTPUT` and charges nothing.\n- If no search source completes, or source candidates cannot form valid job\n  rows, the Actor writes `OUTPUT` and then marks the run as failed.\n- Rows from a later run are new observations. The Actor does not update rows in\n  an earlier dataset.\n\nThe Actor matches each detail section with Google's job ID. If Google omits\nthat ID and several jobs have the same title, the Actor leaves uncertain\n`description`, `tags`, and `applyOptions` values empty.\n\n### ✅ Tested behavior\n\nThe current release passed a fixed 100-row cloud check. It saved 100 rows with\n100 unique Google job IDs and 100 result billing events. The check also\nconfirmed the public field order, nested application-link order, and absence of\nsecret keys in the dataset and run summary.\n\nLive job results can change between runs. This check confirms the release\nprocess. It does not promise that each query will return 10 jobs.\n\n## 🔒 Public-source boundary\n\nThe Actor reads public, logged-out Google Jobs results. It does not:\n\n- Ask for a Google login, browser cookies, or account credentials.\n- Access private or account-only data.\n- Ask for an external search API key.\n- Solve access challenges or bypass access controls.\n- Open application pages or check their contents.\n- Add candidate scores or make hiring decisions.\n\n## ⚠️ Limits and interpretation\n\n- Google controls which jobs and fields are visible for each search.\n- Google can stop pagination before the requested row limit.\n- Duplicate jobs across queries can reduce the final row count.\n- Descriptions, salary text, schedules, and application links can be missing.\n- Google can change, remove, localize, or limit results at any time.\n- Country, language, query wording, and location can change the results.\n- The Google market localizes results. It does not guarantee that every job is\n  in that country.\n- Application URLs are links supplied by Google. Check each destination before\n  you use it in an automated process.\n- `maxItems` is a saved-row limit. It is not a promise that the source contains\n  that many jobs.\n\n## ❓ FAQ\n\n### Does this Actor need a Google login, cookies, or an external API key?\n\nNo.\n\n### Why did I get fewer jobs than requested?\n\nGoogle can return fewer jobs, repeat the same job across searches, omit a Jobs\nresult page, or limit the visible results. Check `sourceStates`,\n`candidatesSeen`, `duplicatesSkipped`, `invalidCandidatesSkipped`, `rowsSaved`,\nand `stopReason` in `OUTPUT`.\n\n### How do I collect more than 10 jobs?\n\nUse several focused queries. Search related job titles separately. The Actor\nremoves repeated Google job IDs across those searches.\n\n### Where are the direct application links?\n\nOpen the raw dataset row and read `applyOptions`. The default Results table does\nnot show this nested list.\n\n### Can I run the same searches on a schedule?\n\nYes. Use an Apify schedule. Use `jobId` to compare jobs across runs and\n`observedAt` to record when each row was collected.\n\n## ⚖️ Responsible use\n\nThis Actor is an independent tool. It is not affiliated with or endorsed by\nGoogle. You are responsible for your use of the data and for compliance with\napplicable laws and source terms.\n\n## 🛟 Support\n\nInclude the Apify run ID, a small input sample, what you expected, and what you\nreceived. State whether the issue affects one row or the full run. Do not send\npasswords, cookies, or account credentials.\n\n## 🔗 Related job scrapers\n\nCompare public job and freelance demand across these sources.\n\n### Compare jobs across sources\n\n- [LinkedIn Jobs Scraper](https://apify.com/chronometrica/linkedin-jobs-scraper)\n  — find public job listings, employers, locations, and company details.\n- [Indeed Jobs Scraper](https://apify.com/chronometrica/indeed-jobs-scraper)\n  — find public Indeed jobs, salaries, skills, and apply links.\n- [Naukri Jobs Scraper](https://apify.com/chronometrica/naukri-jobs-scraper)\n  — find public India job listings, employers, locations, and skills.\n- [Upwork Job Scraper](https://apify.com/chronometrica/upwork-job-scraper)\n  — find fresh freelance jobs, skills, budgets, and client signals.\n",
    "actorDefinition": {
      "$schema": "https://apify.com/schemas/v1/actor.ide.json",
      "actorSpecification": 1,
      "name": "google-jobs-scraper",
      "title": "Google Jobs Scraper - Job Search & Apply Links",
      "description": "Search Google Jobs by keyword and location. Export unique jobs with titles, companies, full descriptions, salary text, posting dates, and direct ATS or job-board apply links. No Google login or external API key required.",
      "seoTitle": "Google Jobs Scraper | Search Jobs & Apply Links",
      "seoDescription": "Scrape Google Jobs by keyword and location. Export job titles, companies, descriptions, salaries, posting dates, and direct ATS or job board apply links.",
      "categories": [
        "JOBS"
      ],
      "version": "0.2",
      "buildTag": "latest",
      "defaultMemoryMbytes": 256,
      "maxMemoryMbytes": 256,
      "defaultRunOptions": {
        "build": "latest",
        "timeoutSecs": 1800,
        "memoryMbytes": 256
      },
      "exampleRunInput": {
        "contentType": "application/json; charset=utf-8",
        "body": "{\n  \"market\": \"CA-en\",\n  \"queries\": [\n    \"data analyst\"\n  ],\n  \"location\": \"Toronto, Ontario, Canada\",\n  \"maxItems\": 10\n}"
      },
      "input": {
        "title": "Google Jobs search",
        "type": "object",
        "schemaVersion": 1,
        "properties": {
          "market": {
            "title": "1. Google market",
            "type": "string",
            "description": "Select a tested Google country site and result language. This setting does not guarantee that every job is in this country.",
            "editor": "select",
            "sectionCaption": "Choose jobs",
            "sectionDescription": "First select the Google market and language. Then enter job titles or keywords. The optional location applies to every search. Maximum saved jobs is one shared result and billing limit for the run.",
            "default": "US-en",
            "enum": [
              "DZ-fr",
              "AR-es",
              "AT-de",
              "BS-en",
              "BH-ar",
              "BD-en",
              "BY-ru",
              "BE-nl",
              "BE-fr",
              "BJ-fr",
              "BO-es",
              "BW-en",
              "BR-pt",
              "BF-fr",
              "CM-fr",
              "CA-en",
              "CA-fr",
              "CL-es",
              "CO-es",
              "CR-es",
              "CI-fr",
              "CD-fr",
              "DK-da",
              "DO-es",
              "EC-es",
              "EG-ar",
              "SV-es",
              "ET-en",
              "FR-fr",
              "DE-de",
              "GH-en",
              "GR-el",
              "GT-es",
              "HT-fr",
              "HN-es",
              "HK-en",
              "IN-en",
              "ID-id",
              "IT-it",
              "JM-en",
              "JP-ja",
              "JO-ar",
              "KZ-ru",
              "KE-en",
              "KW-ar",
              "KG-ru",
              "LB-ar",
              "LS-en",
              "MW-en",
              "MY-en",
              "ML-fr",
              "MU-en",
              "MX-es",
              "MA-fr",
              "NA-en",
              "NL-nl",
              "NG-en",
              "OM-ar",
              "PK-en",
              "PA-es",
              "PY-es",
              "PE-es",
              "PH-en",
              "PT-pt",
              "PR-es",
              "QA-ar",
              "RU-ru",
              "RW-en",
              "SA-ar",
              "SN-fr",
              "SC-en",
              "SG-en",
              "SO-en",
              "ZA-en",
              "ES-es",
              "LK-en",
              "CH-fr",
              "CH-de",
              "CH-it",
              "TW-zh-TW",
              "TZ-en",
              "TH-th",
              "TG-fr",
              "TT-en",
              "TN-fr",
              "UG-en",
              "AE-en",
              "GB-en",
              "US-en",
              "UY-es",
              "UZ-ru",
              "VE-es",
              "VN-vi",
              "ZM-en",
              "ZW-en"
            ],
            "enumTitles": [
              "Algeria - French",
              "Argentina - Spanish",
              "Austria - German",
              "Bahamas - English",
              "Bahrain - Arabic",
              "Bangladesh - English",
              "Belarus - Russian",
              "Belgium - Dutch",
              "Belgium - French",
              "Benin - French",
              "Bolivia - Spanish",
              "Botswana - English",
              "Brazil - Portuguese",
              "Burkina Faso - French",
              "Cameroon - French",
              "Canada - English",
              "Canada - French",
              "Chile - Spanish",
              "Colombia - Spanish",
              "Costa Rica - Spanish",
              "Côte d’Ivoire - French",
              "Democratic Republic of the Congo - French",
              "Denmark - Danish",
              "Dominican Republic - Spanish",
              "Ecuador - Spanish",
              "Egypt - Arabic",
              "El Salvador - Spanish",
              "Ethiopia - English",
              "France - French",
              "Germany - German",
              "Ghana - English",
              "Greece - Greek",
              "Guatemala - Spanish",
              "Haiti - French",
              "Honduras - Spanish",
              "Hong Kong - English",
              "India - English",
              "Indonesia - Indonesian",
              "Italy - Italian",
              "Jamaica - English",
              "Japan - Japanese",
              "Jordan - Arabic",
              "Kazakhstan - Russian",
              "Kenya - English",
              "Kuwait - Arabic",
              "Kyrgyzstan - Russian",
              "Lebanon - Arabic",
              "Lesotho - English",
              "Malawi - English",
              "Malaysia - English",
              "Mali - French",
              "Mauritius - English",
              "Mexico - Spanish",
              "Morocco - French",
              "Namibia - English",
              "Netherlands - Dutch",
              "Nigeria - English",
              "Oman - Arabic",
              "Pakistan - English",
              "Panama - Spanish",
              "Paraguay - Spanish",
              "Peru - Spanish",
              "Philippines - English",
              "Portugal - Portuguese",
              "Puerto Rico - Spanish",
              "Qatar - Arabic",
              "Russia - Russian",
              "Rwanda - English",
              "Saudi Arabia - Arabic",
              "Senegal - French",
              "Seychelles - English",
              "Singapore - English",
              "Somalia - English",
              "South Africa - English",
              "Spain - Spanish",
              "Sri Lanka - English",
              "Switzerland - French",
              "Switzerland - German",
              "Switzerland - Italian",
              "Taiwan - Traditional Chinese",
              "Tanzania - English",
              "Thailand - Thai",
              "Togo - French",
              "Trinidad and Tobago - English",
              "Tunisia - French",
              "Uganda - English",
              "United Arab Emirates - English",
              "United Kingdom - English",
              "United States - English",
              "Uruguay - Spanish",
              "Uzbekistan - Russian",
              "Venezuela - Spanish",
              "Vietnam - Vietnamese",
              "Zambia - English",
              "Zimbabwe - English"
            ]
          },
          "queries": {
            "title": "2. Job titles or keywords",
            "type": "array",
            "description": "Enter one job title, skill, employer, or phrase per line. Do not add the location here when you use the location field below.",
            "editor": "stringList",
            "default": [
              "data analyst"
            ],
            "prefill": [
              "data analyst"
            ],
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "type": "string",
              "minLength": 1,
              "pattern": "\\S",
              "maxLength": 200
            }
          },
          "location": {
            "title": "3. City or region - optional",
            "type": "string",
            "description": "Google adds this location to every search. Leave it blank for broad results in the selected market. Use one location per run.",
            "editor": "textfield",
            "default": "",
            "maxLength": 200
          },
          "maxItems": {
            "title": "Maximum saved jobs (shared limit)",
            "type": "integer",
            "description": "The Actor stops after it saves this many unique jobs across all searches. You pay only for saved jobs. Google can return fewer jobs.",
            "editor": "number",
            "default": 25,
            "minimum": 1,
            "maximum": 1000
          }
        },
        "required": [
          "queries"
        ]
      },
      "output": {
        "actorOutputSchemaVersion": 1,
        "title": "Google Jobs output",
        "properties": {
          "dataset": {
            "type": "string",
            "title": "Job listings",
            "description": "Unique Google job rows saved by this run.",
            "template": "{{links.apiDefaultDatasetUrl}}/items"
          },
          "summary": {
            "type": "string",
            "title": "Run summary",
            "description": "Counts, source states, and the stop reason for this run.",
            "template": "{{links.apiDefaultKeyValueStoreUrl}}/records/OUTPUT"
          }
        }
      },
      "storages": {
        "dataset": {
          "actorSpecification": 1,
          "fields": {
            "$schema": "http://json-schema.org/draft-07/schema#",
            "type": "object",
            "properties": {
              "query": {
                "type": "string"
              },
              "requestedLocation": {
                "type": "string"
              },
              "country": {
                "type": "string"
              },
              "language": {
                "type": "string"
              },
              "jobId": {
                "type": "string"
              },
              "jobUrl": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "title": {
                "type": "string"
              },
              "companyName": {
                "type": "string"
              },
              "location": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "via": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "postedAtRaw": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "scheduleType": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "salaryRaw": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "tags": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "applyOptions": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "title": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "link": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  "required": [
                    "title",
                    "link"
                  ],
                  "additionalProperties": false
                }
              },
              "observedAt": {
                "type": "string",
                "format": "date-time"
              },
              "runId": {
                "type": "string"
              },
              "schemaVersion": {
                "type": "string",
                "enum": [
                  "4.0"
                ]
              },
              "status": {
                "type": "string"
              },
              "statusReason": {
                "type": "string"
              }
            },
            "required": [
              "query",
              "requestedLocation",
              "country",
              "language",
              "jobId",
              "jobUrl",
              "title",
              "companyName",
              "location",
              "via",
              "description",
              "postedAtRaw",
              "scheduleType",
              "salaryRaw",
              "tags",
              "applyOptions",
              "observedAt",
              "runId",
              "schemaVersion",
              "status",
              "statusReason"
            ],
            "additionalProperties": false
          },
          "views": {
            "overview": {
              "title": "Google job listings",
              "transformation": {
                "fields": [
                  "title",
                  "companyName",
                  "location",
                  "postedAtRaw",
                  "salaryRaw",
                  "via",
                  "jobUrl",
                  "query",
                  "observedAt"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "title": {
                    "label": "Job title",
                    "format": "text"
                  },
                  "companyName": {
                    "label": "Company",
                    "format": "text"
                  },
                  "location": {
                    "label": "Job location",
                    "format": "text"
                  },
                  "postedAtRaw": {
                    "label": "Posted",
                    "format": "text"
                  },
                  "salaryRaw": {
                    "label": "Salary",
                    "format": "text"
                  },
                  "via": {
                    "label": "Listing source",
                    "format": "text"
                  },
                  "jobUrl": {
                    "label": "Google job",
                    "format": "link"
                  },
                  "query": {
                    "label": "Search query",
                    "format": "text"
                  },
                  "observedAt": {
                    "label": "Observed",
                    "format": "date"
                  }
                }
              }
            }
          }
        }
      },
      "dockerfile": "../Dockerfile",
      "readme": "# Google Jobs Scraper - Job Search & Apply Links\n\n## 🔎 What does Google Jobs Scraper do?\n\nGoogle Jobs Scraper searches public Google Jobs results by keyword and\nlocation. It saves one clean row for each unique job.\n\nSelect a Google market, add one or more searches, choose an optional shared\nlocation, and set a saved-row limit. Each row can include the job title,\ncompany, location, full description, salary text, schedule, posting age,\nGoogle job URL, and direct application options.\n\nUse it to:\n\n- 🔔 Build scheduled job alerts.\n- 🧑‍💼 Monitor hiring by company, role, or location.\n- 📊 Study public job demand and salary text.\n- 🧱 Add job listings to a database, dashboard, or job board.\n- 🚀 Find direct employer, career-site, and job-board application links.\n- 📦 Export data as JSON, JSONL, CSV, Excel, XML, RSS, or HTML.\n\nThe Actor reads public, logged-out Google Jobs results. It does not need a\nGoogle account, browser cookies, or an external API key.\n\n## 📦 What Google job data can I extract?\n\nEach default-dataset row represents one unique Google job found during one\nActor run.\n\n| Data group           | Example fields                                           |\n| -------------------- | -------------------------------------------------------- |\n| 🔍 Search context    | `query`, `requestedLocation`, `country`, `language`      |\n| 🆔 Job identity      | `jobId`, `jobUrl`                                        |\n| 💼 Job details       | `title`, `companyName`, `location`, `via`, `description` |\n| 💰 Job facts         | `postedAtRaw`, `scheduleType`, `salaryRaw`, `tags`       |\n| 🚀 Application links | `applyOptions[].title`, `applyOptions[].link`            |\n| 🚦 Row state         | `status`, `statusReason`                                 |\n| 🕐 Run context       | `observedAt`, `runId`, `schemaVersion`                   |\n\nMissing public values stay `null` or use an empty list. The Actor does not\nguess missing values or replace them with zero.\n\n### 🚀 Direct application links\n\nThe `applyOptions` list contains the application pages that Google supplies.\nEach item has a provider `title` and a direct `link`. The list can include an\nemployer applicant tracking system (ATS), a company career page, or a job\nboard. An ATS is software that an employer uses to receive job applications.\n\nOne job can have several application options. The raw dataset row contains the\nfull list. The default Results table does not show this nested field.\n\n## ⚙️ Can I use this Actor through an API?\n\nYes. Run the Actor in Apify Console or connect it to the Apify API, Python SDK,\nJavaScript SDK, webhooks, schedules, or Apify integrations.\n\nKeep your Apify token in an environment variable or secret store. Do not place\nit in source code.\n\n```bash\ncurl -X POST \\\n  \"https://api.apify.com/v2/acts/chronometrica~google-jobs-scraper/runs?token=$APIFY_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"market\": \"CA-en\",\n    \"queries\": [\"data analyst\", \"business intelligence analyst\"],\n    \"location\": \"Toronto, Ontario, Canada\",\n    \"maxItems\": 25\n  }'\n```\n\nUse the returned run ID to check the run, read the default dataset, or attach a\nwebhook. The same JSON input works with the Apify Python and JavaScript clients.\n\n## 🎯 Why scrape Google Jobs?\n\nGoogle Jobs brings listings from employers, career sites, and job boards into\none search view. This can give you broader public job coverage than one job\nboard.\n\n| Use case                 | How the data helps                                          |\n| ------------------------ | ----------------------------------------------------------- |\n| 🔔 Job alerts            | Find new jobs for a role and location on a schedule.        |\n| 🧑‍💼 Recruiting research   | Track companies, roles, locations, and hiring language.     |\n| 📊 Labor-market analysis | Compare visible demand, salary text, and posting activity.  |\n| 🧱 Job-board feeds       | Load unique job rows and direct application choices.        |\n| 🕵️ Employer monitoring   | Watch hiring for selected companies or role groups.         |\n| 🤖 Automated workflows   | Send jobs to a database, spreadsheet, CRM, or alert system. |\n\nThe Actor collects source data. It does not rank candidates, assess people, or\nmake hiring decisions.\n\n## 💵 Pricing\n\nGoogle Jobs Scraper uses pay-per-result pricing. One result means one unique\njob row saved to the default dataset. The price depends on your Apify plan.\n\n| Apify plan | Price per 1,000 saved jobs |\n| ---------- | -------------------------- |\n| Free       | $5.00                      |\n| Starter    | $4.50                      |\n| Scale      | $4.00                      |\n| Business   | $3.50                      |\n\nDuplicate and invalid candidates do not create a result charge. Use `maxItems`\nto set a firm saved-row limit. Check the Store **Pricing** tab for the current\nrate before a production run.\n\nStart with `maxItems: 10` when you test a new search.\n\n## 🚀 How do I use Google Jobs Scraper?\n\n1. Open **Google Jobs Scraper** in Apify Console.\n2. Select the Google market and result language.\n3. Enter one or more job titles or keywords.\n4. Enter one optional city or region for all searches.\n5. Set the maximum number of jobs to save.\n6. Click **Start**.\n7. Open **Output** to inspect the dataset and run summary.\n8. Download the data or connect the run to another tool.\n\n## ⬇️ Input\n\n```json\n{\n  \"market\": \"CA-en\",\n  \"queries\": [\"data analyst\", \"business intelligence analyst\"],\n  \"location\": \"Toronto, Ontario, Canada\",\n  \"maxItems\": 25\n}\n```\n\n| Setting    | What it controls                                                                       |\n| ---------- | -------------------------------------------------------------------------------------- |\n| `market`   | Tested Google country site and result language. It is not a strict job-country filter. |\n| `queries`  | One to 100 job-title or keyword searches. Each query can have up to 200 characters.    |\n| `location` | One optional city, region, or country for all searches. A city gives precise results.  |\n| `maxItems` | Maximum unique rows saved across the run. The allowed range is 1 to 1,000.             |\n\nThe form offers 95 tested country and language pairs. Countries with more than\none tested language have one option for each language. The Actor still accepts\nthe earlier `country` and `language` API fields when they form one supported\nmarket pair.\n\nThe Actor removes duplicate queries without treating letter case as different.\nIt gives active searches balanced row turns in query order. It requests only\nthe search pages that can still contribute to the shared saved-row limit. This\nprevents one broad search from using the full limit and avoids paying for pages\nthat cannot add a saved row.\n\nThe Actor follows Google's continuation tokens until it reaches `maxItems`,\nthe source ends, or the 100-page per-query safety cap.\n\n## ⬆️ Output sample\n\nResults are stored in the default dataset. Field order is stable for JSON,\nCSV, spreadsheet, API, and database work.\n\n```json\n{\n  \"query\": \"data analyst\",\n  \"requestedLocation\": \"Toronto, Ontario, Canada\",\n  \"country\": \"CA\",\n  \"language\": \"en\",\n  \"jobId\": \"example-job-id\",\n  \"jobUrl\": \"https://www.google.com/search?ibp=htl;jobs&htidocid=example-job-id\",\n  \"title\": \"Data Analyst\",\n  \"companyName\": \"Example Company\",\n  \"location\": \"Toronto, ON\",\n  \"via\": \"Example Careers\",\n  \"description\": \"Analyze business data and prepare reports.\",\n  \"postedAtRaw\": \"Posted 2 days ago\",\n  \"scheduleType\": \"Full-time\",\n  \"salaryRaw\": \"$70,000-$85,000 a year\",\n  \"tags\": [\"2 days ago\", \"Full-time\", \"No degree mentioned\"],\n  \"applyOptions\": [\n    {\n      \"title\": \"Apply on Example Careers\",\n      \"link\": \"https://jobs.example.com/data-analyst\"\n    }\n  ],\n  \"observedAt\": \"2026-08-08T23:40:50.000Z\",\n  \"runId\": \"example-run-id\",\n  \"schemaVersion\": \"4.0\",\n  \"status\": \"succeeded\",\n  \"statusReason\": \"Job listing returned by Google Jobs.\"\n}\n```\n\n### 📌 Run summary\n\nThe `OUTPUT` record is stored in the default key-value store. It reports:\n\n- `queriesReceived`: number of unique searches received.\n- `sourceStates`: result state for each search.\n- `pagesRequested`: source batches requested.\n- `candidatesSeen`: jobs found before validation and duplicate removal.\n- `rowsSaved`: unique job rows saved and charged.\n- `duplicatesSkipped`: repeated job IDs not saved or charged.\n- `invalidCandidatesSkipped`: candidates without the required identity fields.\n- `stopReason`: `saved_row_cap_reached` or `sources_exhausted`.\n- `startedAt` and `finishedAt`: run times in ISO format.\n\nUse this record to tell the difference between a small result set, duplicate\njobs, a source error, and a completed saved-row limit.\n\nA `partial` source state means that Google returned useful jobs before a later\ncontinuation request failed. The Actor keeps and charges only those useful\nrows.\n\n## 🚦 Status, identity, and data quality\n\n- Every saved row has a valid `jobId`, `title`, and `companyName`.\n- Every saved row has `status: \"succeeded\"`.\n- The Actor uses `jobId` to remove duplicates across all queries in one run.\n- `observedAt` is the collection time. `postedAtRaw` is source text, not a\n  normalized posting time.\n- `tags` contains the exact job tags that Google supplies. The Actor does not\n  infer or classify these values.\n- `description`, salary, schedule, location, source, job URL, and application\n  options can be missing.\n- If completed searches contain no jobs, the Actor succeeds with\n  `status: \"no_results\"` in `OUTPUT` and charges nothing.\n- If no search source completes, or source candidates cannot form valid job\n  rows, the Actor writes `OUTPUT` and then marks the run as failed.\n- Rows from a later run are new observations. The Actor does not update rows in\n  an earlier dataset.\n\nThe Actor matches each detail section with Google's job ID. If Google omits\nthat ID and several jobs have the same title, the Actor leaves uncertain\n`description`, `tags`, and `applyOptions` values empty.\n\n### ✅ Tested behavior\n\nThe current release passed a fixed 100-row cloud check. It saved 100 rows with\n100 unique Google job IDs and 100 result billing events. The check also\nconfirmed the public field order, nested application-link order, and absence of\nsecret keys in the dataset and run summary.\n\nLive job results can change between runs. This check confirms the release\nprocess. It does not promise that each query will return 10 jobs.\n\n## 🔒 Public-source boundary\n\nThe Actor reads public, logged-out Google Jobs results. It does not:\n\n- Ask for a Google login, browser cookies, or account credentials.\n- Access private or account-only data.\n- Ask for an external search API key.\n- Solve access challenges or bypass access controls.\n- Open application pages or check their contents.\n- Add candidate scores or make hiring decisions.\n\n## ⚠️ Limits and interpretation\n\n- Google controls which jobs and fields are visible for each search.\n- Google can stop pagination before the requested row limit.\n- Duplicate jobs across queries can reduce the final row count.\n- Descriptions, salary text, schedules, and application links can be missing.\n- Google can change, remove, localize, or limit results at any time.\n- Country, language, query wording, and location can change the results.\n- The Google market localizes results. It does not guarantee that every job is\n  in that country.\n- Application URLs are links supplied by Google. Check each destination before\n  you use it in an automated process.\n- `maxItems` is a saved-row limit. It is not a promise that the source contains\n  that many jobs.\n\n## ❓ FAQ\n\n### Does this Actor need a Google login, cookies, or an external API key?\n\nNo.\n\n### Why did I get fewer jobs than requested?\n\nGoogle can return fewer jobs, repeat the same job across searches, omit a Jobs\nresult page, or limit the visible results. Check `sourceStates`,\n`candidatesSeen`, `duplicatesSkipped`, `invalidCandidatesSkipped`, `rowsSaved`,\nand `stopReason` in `OUTPUT`.\n\n### How do I collect more than 10 jobs?\n\nUse several focused queries. Search related job titles separately. The Actor\nremoves repeated Google job IDs across those searches.\n\n### Where are the direct application links?\n\nOpen the raw dataset row and read `applyOptions`. The default Results table does\nnot show this nested list.\n\n### Can I run the same searches on a schedule?\n\nYes. Use an Apify schedule. Use `jobId` to compare jobs across runs and\n`observedAt` to record when each row was collected.\n\n## ⚖️ Responsible use\n\nThis Actor is an independent tool. It is not affiliated with or endorsed by\nGoogle. You are responsible for your use of the data and for compliance with\napplicable laws and source terms.\n\n## 🛟 Support\n\nInclude the Apify run ID, a small input sample, what you expected, and what you\nreceived. State whether the issue affects one row or the full run. Do not send\npasswords, cookies, or account credentials.\n\n## 🔗 Related job scrapers\n\nCompare public job and freelance demand across these sources.\n\n### Compare jobs across sources\n\n- [LinkedIn Jobs Scraper](https://apify.com/chronometrica/linkedin-jobs-scraper)\n  — find public job listings, employers, locations, and company details.\n- [Indeed Jobs Scraper](https://apify.com/chronometrica/indeed-jobs-scraper)\n  — find public Indeed jobs, salaries, skills, and apply links.\n- [Naukri Jobs Scraper](https://apify.com/chronometrica/naukri-jobs-scraper)\n  — find public India job listings, employers, locations, and skills.\n- [Upwork Job Scraper](https://apify.com/chronometrica/upwork-job-scraper)\n  — find fresh freelance jobs, skills, budgets, and client signals.\n"
    },
    "buildNumber": "0.2.30",
    "actVersion": {
      "sourceType": "SOURCE_FILES",
      "buildTag": "release-candidate",
      "versionNumber": "0.2"
    },
    "usage": {
      "ACTOR_COMPUTE_UNITS": 0.010294444444444445
    }
  }
}