# Jobs.ge Georgia Jobs Scraper (`zinin/jobs-ge`) Actor

Search Jobs.ge (Georgia, the country's oldest job board) and get public job listings: title, employer, published date, application deadline, URL. Unofficial, independent tool — not affiliated with, endorsed by, or sponsored by Jobs.ge.

- **URL**: https://apify.com/zinin/jobs-ge.md
- **Developed by:** [Tim Zinin](https://apify.com/zinin) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 8 total users, 3 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.70 / 1,000 job founds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Jobs.ge Georgia Jobs Scraper

Get public job listings from Jobs.ge with a title, employer, publication date, application deadline and direct link. Narrow the search by category, location, title keywords and publication date. No Jobs.ge login is required.

### What you get

- **Search across listing pages.** The Actor follows the source page count until it has enough unique matching jobs, reaches the final source page, or reaches its 20-page safety limit. Keyword/date filters are applied before the result limit.
- **Category and location filters.** Select Jobs.ge's own categories and location groups, including Tbilisi, regions, Abroad and Remote.
- **Keyword matching against real titles.** Give it one or more search terms and it returns only the postings whose title matches — each matched row also tells you which of your terms it matched.
- **Application deadlines, not just posting dates.** Jobs.ge publishes both a "Published" and a "Deadline" date for every posting; this Actor resolves both into proper ISO timestamps (the source itself never prints a year — this Actor works it out from its own clock).
- **Optional full description.** Turn on one setting and this Actor also visits each matched job's own page for the free-text posting body — off by default so a run stays fast.
- **Honest "no results."** A search whose keyword matched nothing pushes a `found: false` row with no error — a search the source failed to answer pushes `found: false` with a real error message. The two never look the same.
- Runs on Apify: schedule it, monitor it, call it from the API or the MCP server, export to JSON, CSV or Excel, or push results straight into your own pipeline.

### How to run it

1. Click **Try for free** — no card needed on the free plan.
2. Optionally select **Job category**, **Location / region**, and **Published within days**. Add title keywords such as `manager` or `driver` if needed.
3. Press **Start**. Results appear in the dataset — read them in the UI, pull them from the API, or have a webhook push them onward.

### Pricing

Pay-per-event: a fixed fee per run start plus a per-job-found fee — the live numbers are on this Actor's own Store page (Pricing tab), set at publish time.

A search with no matches, a source error, or an unconfirmed source filter produces a `found: false` diagnostic without a result-found fee. The separate Actor-start fee still applies. Result fees are charged for job rows actually delivered.

### Input

**Filter by publication date:** set `posted_within_days` to `1`, `7`, `30`, or another integer from 1 to 365. This uses calendar dates in UTC: `1` means today; `7` includes today and the previous 6 days. The filter runs before `max_items`. Omit it to disable the date filter. Jobs with an unknown publication date are excluded when enabled. Jobs.ge supplies dates without publication times, so this is not a rolling hourly window.

For example, request up to 100 jobs from the last seven calendar dates:

```json
{
  "keywords": [],
  "language": "en",
  "posted_within_days": 7,
  "max_items": 100,
  "fetch_full_description": false
}
```

| Field | Required | What it does |
|---|---|---|
| `fetch_job_category` | no | Default `false`. Enable native category lookup for returned jobs when no category filter is selected; slower, with bounded requests and null/partial for unresolved categories. |
| `category` | no | Jobs.ge category, such as `it_programming`, `sales_procurement`, or `medicine_pharmacy`. Choose from the dropdown. Omit or use `all` for every category. |
| `location` | no | Jobs.ge location group, such as `tbilisi`, `imereti`, `adjara`, `remote`, or `abroad`. For Kutaisi choose Imereti; for Batumi choose Adjara. Omit or use `all` for every location. |
| `posted_within_days` | no | Most recent 1–365 calendar dates in UTC. `7` includes today and the previous six dates. |
| `keywords` | no | Search terms matched against job titles across the scanned listing pages, e.g. `manager`, `driver`. Up to 10 per run. Leave empty to disable the title filter. |
| `language` | no | Which Jobs.ge interface to read — English (`en`) or Georgian (`ge`). Most postings are written in Georgian regardless of interface language, which is a fact of the source, not something this Actor translates. |
| `max_items` | no | Maximum job rows after all filters (default 30, maximum 320). |
| `fetch_full_description` | no | Visit each matched job's own page for the full description text (slower — one extra request per job). Off by default; without it, `description` is `null` — Jobs.ge's listing page carries no snippet at all, not even a teaser. |

```json
{
    "keywords": [],
    "language": "en",
    "max_items": 5,
    "fetch_full_description": false
}
```

For medicine and pharmacy jobs in Imereti published within the last seven calendar dates:

```json
{
  "category": "medicine_pharmacy",
  "location": "imereti",
  "posted_within_days": 7,
  "max_items": 20,
  "language": "en"
}
```

Category and location use the site's own classification. If the returned search page does not confirm your selected filters, the Actor returns a diagnostic and withholds job results. `location` is the explicit label beside the job title, when Jobs.ge supplies one. `source_region` separately records a confirmed selected region; it is not substituted for a city.

**Coverage:** the Actor scans up to 20 listing pages and stops early when `max_items` is satisfied. It preserves and confirms category/location filters on every page and removes duplicate job IDs. Each result states `listing_pages_fetched`, `listing_pages_available`, `listing_scan_complete`, and `listing_stop_reason`. Completion refers to the page count observed when the search began; live postings can change during a run. If a later page fails, repeats, loses the requested filters, or the safety limit is reached, useful results are marked `partial` and a free diagnostic explains the incomplete search. A requested item cap is a normal stop, not a source error.

#### Include category and location fields

Location labels are extracted automatically from the listing. To discover categories without limiting the search to one category, enable `fetch_job_category`. The lookup checks returned job IDs against Jobs.ge's category listings, with at most 40 extra page fetches and at most 20 pages per category. Category fetches have no automatic retry, and the lookup stops starting new pages after its 60-second collection budget. It stops once each returned job has an observed membership. It is slower than the default run. `category_lookup_status` is `observed_membership` or `unresolved`; unresolved rows retain useful job data, null category, and a partial warning. Missing labels are never guessed.

```json
{
  "posted_within_days": 7,
  "max_items": 5,
  "language": "en",
  "fetch_job_category": true
}
```

### Output

One dataset row per job found. This is a real row from a real run (default settings, no keyword filter):

```json
{
    "found": true,
    "url": "https://www.jobs.ge/en/?view=jobs&id=741288",
    "job_id": "741288",
    "title": "Cashier-Consultant",
    "company": "Way Mart",
    "company_url": "https://www.jobs.ge/en/?view=client&client=way-mart",
    "location": null,
    "country": null,
    "employment_type": null,
    "posted_date": "2026-07-30T00:00:00.000Z",
    "salary_raw": null,
    "description": null,
    "description_is_teaser": null,
    "source_board": "jobs-ge",
    "scraped_at": "2026-07-30T14:22:28.264Z",
    "deadline_date": "2026-08-30T00:00:00.000Z",
    "matched_keywords": []
}
```

| Field | What it means |
|---|---|
| `found` | Whether this row is a real job (`true`) or a notice/error row (`false`) |
| `url` | Direct link to the job posting — the unique key for this row |
| `job_id` | Jobs.ge's own job identifier |
| `title` | Job title |
| `company` | Hiring company name, or `null` — some posters hide the company name entirely |
| `company_url` | Link to the company's own profile page on Jobs.ge, or `null` when the poster is anonymous |
| `location` | Explicit source label beside the job title, such as `Rustavi`; null when absent. No inference from the description. |
| `location_source` | `jobs_ge_listing_label` when a source label was extracted; otherwise null. |
| `source_region` | Confirmed selected Jobs.ge location group, or null without a region filter. |
| `category`, `category_id` | Observed Jobs.ge category name and ID. Available from a selected category filter, or opt-in category lookup. Null when unconfirmed. |
| `category_source`, `category_url` | Provenance: native category listing and its URL. One observed membership, not all possible categories. |
| `country` | `null` — not extracted by this Actor |
| `employment_type` | `null` — not extracted by this Actor |
| `posted_date` | ISO 8601 timestamp. Jobs.ge never prints a year on this date — this Actor reconstructs it from the run's own clock, so it is never off by more than the wrap of a single year boundary |
| `salary_raw` | Almost always `null` — Jobs.ge essentially never publishes a structured salary field (it occasionally appears buried inside free-text description, which this field does not read); this Actor never estimates or invents a figure |
| `description` | Free-text posting body, up to 600 characters — only populated when `fetch_full_description` is on, otherwise `null` (the listing page itself carries no snippet, not even a teaser) |
| `description_is_teaser` | Always `false` when `description` is set — Jobs.ge's own detail page gives the real posting text directly, no teaser step |
| `source_board` | Always `"jobs-ge"` |
| `scraped_at` | When this Actor fetched the row |
| `deadline_date` | ISO 8601 timestamp of the posting's application deadline, or `null` if unparseable. Jobs.ge prints this alongside the posted date with the same no-year quirk |
| `matched_keywords` | Array of your search keywords that matched this row's title — empty when you searched with no keyword filter at all |
| `partial` / `partial_reason` | Present when a source scan is incomplete or a requested detail page fails. Confirmed job rows remain useful and are billed once; the extra diagnostic row is free. |
| `listing_pages_fetched` / `listing_pages_available` | Successfully parsed listing pages and the source page count observed at the start (`null` when unknown) |
| `listing_scan_complete` / `listing_stop_reason` | Whether the observed source page range was fully scanned and why collection stopped; `max_items` means the requested result cap was satisfied |

A keyword that matched nothing pushes:

```json
{ "input": ["zzznonexistentjobtitle999xyz"], "found": false, "error": "", "source_board": "jobs-ge", "scraped_at": "..." }
```

A request Jobs.ge failed to answer (verified live: an excessively long keyword trips the source's own request-line length limit) pushes an error row with a non-empty `error` (e.g. `"http 414"`) — never the same shape as "no matches."

### Other tools we built

#### Related tools

Related tools for adjacent workflows in jobs and hiring.

| Actor | What it does |
|---|---|
| [jobs.ch Swiss Jobs Scraper](https://apify.com/zinin/jobs-ch-swiss) | Pair it in the jobs and hiring workflow: Search jobs.ch (Switzerland) by keyword and get public job listings: title, company, location, employment... |
| [Computrabajo LatAm Jobs Scraper](https://apify.com/zinin/computrabajo-jobs) | Pair it in the jobs and hiring workflow: Search Computrabajo (Mexico, Colombia, Chile, Argentina, Peru) by keyword and get public job listings:... |
| [Boss.az Azerbaijan Jobs Scraper](https://apify.com/zinin/boss-az) | Pair it in the jobs and hiring workflow: Walk boss.az's own job sitemap and pull public job listings from Azerbaijan: title, employer, location,... |
| [XING Jobs (DACH) Scraper](https://apify.com/zinin/xing-jobs) | Pair it in the jobs and hiring workflow: Walk xing.com's own job sitemap and pull public job listings from Germany/Austria/Switzerland: title,... |
| [Job Postings Aggregator](https://apify.com/zinin/job-postings-aggregator) | Pair it in the jobs and hiring workflow: Pull every open role from a company's public applicant-tracking system (Greenhouse, Lever, Ashby) and... |

### FAQ

**Does it need an API key / login?** No — it reads Jobs.ge's public, server-rendered listing page.

**Can I filter by location when the output's `location` is null?** Yes. The input uses Jobs.ge's own location selector. It narrows the search without inferring a city, country, employment type or salary from each posting's text; unlabelled values remain null. Explicit location labels are now extracted automatically.

**Why can `keywords` return 0 rows when Jobs.ge has live postings?** Keywords match titles across the scanned pages. A complete scan with no matches produces a free empty-result notice. If coverage could not be completed, the notice explicitly says so rather than claiming no matching jobs exist.

**Are postings in English?** Interface language (`en`/`ge`) only changes which language Jobs.ge's own labels and dates are shown in — the postings themselves are predominantly written in Georgian either way. That is Jobs.ge's own behavior, not a translation gap in this Actor.

**How fresh is the data?** Live at run time — every call fetches Jobs.ge's current listing page.

**Can I call it from an AI agent?** Yes — standard Apify Actor, callable from the Apify API, the SDK, or the Apify MCP server.

**What this is NOT.** It does not apply to jobs on your behalf, does not cover job boards outside Jobs.ge, and does not invent a salary figure when the source has none.

Found a wrong result, or need a check we don't run? Open an issue on this Actor's page.

***

Built by [zinin](https://apify.com/zinin). Questions? Telegram [@timzinin](https://t.me/timzinin).

# Actor input Schema

## `posted_within_days` (type: `integer`):

Keep jobs from the most recent N calendar dates in UTC: 1 = today, 7 = today and the previous 6 days, 30 = today and the previous 29 days. Applied before Max items. Omit to disable the date filter. Search scans up to 20 listing pages and stops when Max items is satisfied; incomplete scans are disclosed. Jobs with an unknown publication date are excluded when this filter is enabled. Jobs.ge supplies dates, not publication times; this is not a rolling hourly window.

## `keywords` (type: `array`):

Words/phrases to match against job titles across scanned source pages. Leave empty to disable the title filter. Scans up to 20 pages, stopping when Max items is satisfied; incomplete scans are disclosed. Runtime cap: 10 keywords.

## `language` (type: `string`):

Which Jobs.ge interface to read: English (/en/) or Georgian (/ge/). Most postings are written in Georgian regardless of interface language — that is a fact of the source, not a translation this Actor performs.

## `max_items` (type: `integer`):

Maximum job rows returned after category, location, keyword, and publication-date filtering.

## `fetch_full_description` (type: `boolean`):

Visit each matched job's detail page for the full description text (slower — one extra request per job). When off, description is null (Jobs.ge's listing page carries no description snippet at all, not even a teaser).

## `category` (type: `string`):

Narrow the search using Jobs.ge's own job category. Omit or choose All for every category. Combined with Location, keywords, and publication date before Max items.

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

Narrow the search using Jobs.ge's own location groups: Tbilisi, regions, Abroad, or Remote. For Kutaisi choose Imereti; for Batumi choose Adjara AR. These are source-defined search groups, not a normalized city field in each output row. Omit or choose All for every location.

## `fetch_job_category` (type: `boolean`):

Populate category from confirmed Jobs.ge category-list membership even when no category filter is selected. Adds up to 40 listing requests across categories (up to 20 per category), so it is slower. Reports the first observed membership, not every possible category. Unresolved categories stay null and are marked partial. A selected category filter already supplies its confirmed category without these extra requests.

## Actor input object example

```json
{
  "keywords": [],
  "language": "en",
  "max_items": 5,
  "fetch_full_description": false,
  "fetch_job_category": false
}
```

# Actor output Schema

## `results` (type: `string`):

API URL for the default dataset items produced by this run.

# 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 = {
    "keywords": [],
    "language": "en",
    "max_items": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("zinin/jobs-ge").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 = {
    "keywords": [],
    "language": "en",
    "max_items": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("zinin/jobs-ge").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 '{
  "keywords": [],
  "language": "en",
  "max_items": 5
}' |
apify call zinin/jobs-ge --silent --output-dataset

```

## MCP server setup

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

```

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/VtmKAvJwKUr1xawm3/builds/dcG0uVvZ7bv9Chh8K/openapi.json
